diff options
author | Erik Kundiman <erik@megapahit.org> | 2023-10-04 06:08:09 +0800 |
---|---|---|
committer | Erik Kundiman <erik@megapahit.org> | 2023-10-04 06:08:09 +0800 |
commit | a332b8faefef8a044a2409957b415b62d6a85181 (patch) | |
tree | e76c2d8c9500c700d2d45ea1b4708747854f2f7d /indra/newview | |
parent | bdfd8198eb8fe7128230d562fe37eafac5c52bec (diff) | |
parent | f352fd1090ce4d50db349cdadfa61d66783a20e8 (diff) |
Merge tag '6.6.15-release'
source for viewer 6.6.15.581961
Diffstat (limited to 'indra/newview')
181 files changed, 17791 insertions, 6436 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1e2fcaa3ad..40a858cfeb 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -207,6 +207,7 @@ set(viewer_SOURCE_FILES llfloaterbuyland.cpp llfloatercamera.cpp llfloatercamerapresets.cpp + llfloaterchangeitemthumbnail.cpp llfloaterchatvoicevolume.cpp llfloaterclassified.cpp llfloatercolorpicker.cpp @@ -243,6 +244,7 @@ set(viewer_SOURCE_FILES llfloaterimsession.cpp llfloaterimcontainer.cpp llfloaterinspect.cpp + llfloaterinventorysettings.cpp llfloaterjoystick.cpp llfloaterlagmeter.cpp llfloaterland.cpp @@ -258,12 +260,12 @@ set(viewer_SOURCE_FILES llfloatermyscripts.cpp llfloatermyenvironment.cpp llfloaternamedesc.cpp + llfloaternewfeaturenotification.cpp llfloaternotificationsconsole.cpp llfloaternotificationstabbed.cpp - llfloateroutfitphotopreview.cpp llfloaterobjectweights.cpp llfloateropenobject.cpp - llfloatersimpleoutfitsnapshot.cpp + llfloatersimplesnapshot.cpp llfloaterpathfindingcharacters.cpp llfloaterpathfindingconsole.cpp llfloaterpathfindinglinksets.cpp @@ -278,7 +280,6 @@ set(viewer_SOURCE_FILES llfloaterpreferenceviewadvanced.cpp llfloaterpreviewtrash.cpp llfloaterprofiletexture.cpp - llfloaterproperties.cpp llfloaterregiondebugconsole.cpp llfloaterregioninfo.cpp llfloaterreporter.cpp @@ -350,10 +351,13 @@ set(viewer_SOURCE_FILES llinspectgroup.cpp llinspectobject.cpp llinspectremoteobject.cpp + llinspecttexture.cpp llinspecttoast.cpp llinventorybridge.cpp llinventoryfilter.cpp llinventoryfunctions.cpp + llinventorygallery.cpp + llinventorygallerymenu.cpp llinventoryicon.cpp llinventoryitemslist.cpp llinventorylistitem.cpp @@ -579,6 +583,7 @@ set(viewer_SOURCE_FILES lltextureinfodetails.cpp lltexturestats.cpp lltextureview.cpp + llthumbnailctrl.cpp lltoast.cpp lltoastalertpanel.cpp lltoastgroupnotifypanel.cpp @@ -847,6 +852,7 @@ set(viewer_HEADER_FILES llfloaterbuycurrencyhtml.h llfloaterbuyland.h llfloatercamerapresets.h + llfloaterchangeitemthumbnail.h llfloatercamera.h llfloaterchatvoicevolume.h llfloaterclassified.h @@ -887,6 +893,7 @@ set(viewer_HEADER_FILES llfloaterimsession.h llfloaterimcontainer.h llfloaterinspect.h + llfloaterinventorysettings.h llfloaterjoystick.h llfloaterlagmeter.h llfloaterland.h @@ -902,12 +909,12 @@ set(viewer_HEADER_FILES llfloatermyscripts.h llfloatermyenvironment.h llfloaternamedesc.h + llfloaternewfeaturenotification.h llfloaternotificationsconsole.h llfloaternotificationstabbed.h - llfloateroutfitphotopreview.h llfloaterobjectweights.h llfloateropenobject.h - llfloatersimpleoutfitsnapshot.h + llfloatersimplesnapshot.h llfloaterpathfindingcharacters.h llfloaterpathfindingconsole.h llfloaterpathfindinglinksets.h @@ -922,7 +929,6 @@ set(viewer_HEADER_FILES llfloaterpreferenceviewadvanced.h llfloaterpreviewtrash.h llfloaterprofiletexture.h - llfloaterproperties.h llfloaterregiondebugconsole.h llfloaterregioninfo.h llfloaterreporter.h @@ -992,10 +998,13 @@ set(viewer_HEADER_FILES llinspectgroup.h llinspectobject.h llinspectremoteobject.h + llinspecttexture.h llinspecttoast.h llinventorybridge.h llinventoryfilter.h llinventoryfunctions.h + llinventorygallery.h + llinventorygallerymenu.h llinventoryicon.h llinventoryitemslist.h llinventorylistitem.h @@ -1212,6 +1221,7 @@ set(viewer_HEADER_FILES lltextureinfodetails.h lltexturestats.h lltextureview.h + llthumbnailctrl.h lltoast.h lltoastalertpanel.h lltoastgroupnotifypanel.h diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index e9d5f8c17f..b69c12176f 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -6.6.14 +6.6.15 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 816b7b7c92..28d84aba21 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -5002,17 +5002,6 @@ <key>Value</key> <integer>0</integer> </map> - <key>InventoryInboxToggleState</key> - <map> - <key>Comment</key> - <string>Stores the open/closed state of inventory Received items panel</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> <key>InventoryLinking</key> <map> <key>Comment</key> @@ -5508,6 +5497,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>LastUIFeatureVersion</key> + <map> + <key>Comment</key> + <string>UI Feature Version number for tracking feature notification between viewer builds</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <string></string> + </map> <key>LastFindPanel</key> <map> <key>Comment</key> @@ -15371,6 +15371,17 @@ <key>Value</key> <integer>1</integer> </map> + <key>BatchSizeAIS3</key> + <map> + <key>Comment</key> + <string>Amount of folder ais packs into category subset request</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> + <integer>20</integer> + </map> <key>PoolSizeAIS</key> <map> <key>Comment</key> @@ -15378,7 +15389,7 @@ <key>Type</key> <string>U32</string> <key>Value</key> - <integer>1</integer> + <integer>20</integer> </map> <key>PoolSizeUpload</key> <map> @@ -17107,6 +17118,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>FindOriginalOpenWindow</key> + <map> + <key>Comment</key> + <string>Sets the action for 'Find original' and 'Show in Inventory' (0 - shows item in main Inventory, 1 - opens a new single-folder window)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>StatsReportMaxDuration</key> <map> <key>Comment</key> @@ -17140,5 +17162,27 @@ <key>Value</key> <integer>0</integer> </map> +<key>MultiModeDoubleClickFolder</key> +<map> + <key>Comment</key> + <string>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)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>U32</string> + <key>Value</key> + <integer>0</integer> +</map> +<key>SingleModeDoubleClickOpenWindow</key> +<map> + <key>Comment</key> + <string>Sets the action for Double-click on folder in single-folder view (0 - stays in current window, 1 - opens a new window)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> +</map> </map> </llsd> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index f87fa5b281..77fe601c1e 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -120,6 +120,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; @@ -372,7 +377,7 @@ LLAgent::LLAgent() : mHideGroupTitle(FALSE), mGroupID(), - mInitialized(FALSE), + mInitialized(false), mListener(), mDoubleTapRunTimer(), @@ -448,7 +453,7 @@ LLAgent::LLAgent() : mNextFidgetTime(0.f), mCurrentFidget(0), - mFirstLogin(FALSE), + mFirstLogin(false), mOutfitChosen(FALSE), mVoiceConnected(false), @@ -505,7 +510,7 @@ void LLAgent::init() mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AGENT); - mInitialized = TRUE; + mInitialized = true; } //----------------------------------------------------------------------------- @@ -560,6 +565,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 ea91d2b720..fd3a9b1d7b 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -117,15 +117,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<LLAgentListener> mListener; //-------------------------------------------------------------------- diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index 53397978e0..db99f20775 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -1306,8 +1306,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; @@ -1526,7 +1527,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<void(const LLUUID&)> created_cb) { if (type == LLWearableType::WT_INVALID || type == LLWearableType::WT_NONE) return; @@ -1538,7 +1539,7 @@ void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, con LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); LLAssetType::EType asset_type = wearable->getAssetType(); - LLPointer<LLInventoryCallback> cb; + LLPointer<LLBoostFuncInventoryCallback> cb; if(wear) { cb = new LLBoostFuncInventoryCallback(wear_and_edit_cb); @@ -1547,6 +1548,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 2710262910..e20f5df7fa 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -129,7 +129,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<void(const LLUUID&)> created_cb = NULL); static void editWearable(const LLUUID& item_id); bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body); diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index 005259bcb8..087cfb8d48 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" ///---------------------------------------------------------------------------- @@ -43,11 +47,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::ais_query_item_t> 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() @@ -93,6 +102,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; } @@ -100,7 +113,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 @@ -124,7 +137,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); } @@ -135,6 +148,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; } @@ -170,6 +187,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; } @@ -203,6 +224,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; } @@ -235,6 +260,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; } @@ -279,6 +308,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; } @@ -313,6 +346,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(); @@ -345,6 +382,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(); @@ -368,6 +409,380 @@ void AISAPI::UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t } /*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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string&, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string&, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD (LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, + LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)> + //---- + // _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<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t , const std::string& , LLCore::HttpOptions::ptr_t , LLCore::HttpHeaders::ptr_t)> + //---- + // _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) { LLCoprocedureManager &inst = LLCoprocedureManager::instance(); @@ -418,21 +833,66 @@ 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()) { @@ -474,29 +934,127 @@ 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()) - { + { + bool needs_callback = true; LLUUID id(LLUUID::null); - if (result.has("category_id") && (type == COPYLIBRARYCATEGORY)) - { - id = result["category_id"]; - } + 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; + } - callback(id); + 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); } @@ -506,6 +1064,7 @@ void AISUpdate::clearParseResults() mCatDescendentsKnown.clear(); mCatVersionsUpdated.clear(); mItemsCreated.clear(); + mItemsLost.clear(); mItemsUpdated.clear(); mCategoriesCreated.clear(); mCategoriesUpdated.clear(); @@ -514,6 +1073,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(); @@ -601,24 +1170,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); } } } @@ -636,7 +1218,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 @@ -648,6 +1240,7 @@ void AISUpdate::parseItem(const LLSD& item_map) { mItemsCreated[item_id] = new_item; mCatDescendentDeltas[new_item->getParentUUID()]++; + new_item->setComplete(true); } } else @@ -657,7 +1250,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<LLViewerInventoryItem> new_link(new LLViewerInventoryItem); @@ -671,7 +1264,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 @@ -690,7 +1300,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 { @@ -700,19 +1316,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<LLViewerInventoryCategory> new_cat; - LLViewerInventoryCategory *curr_cat = gInventory.getCategory(category_id); if (curr_cat) { // Default to current values where not provided. @@ -732,13 +1359,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 @@ -751,20 +1423,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()]++; @@ -779,28 +1453,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 { @@ -812,11 +1493,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); } } @@ -833,7 +1514,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(); @@ -841,13 +1522,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); } } } @@ -857,7 +1538,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); } @@ -873,7 +1554,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; } @@ -884,19 +1565,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(), @@ -905,19 +1586,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<LLUUID,S32>::const_iterator catit = mCatDescendentDeltas.begin(); catit != mCatDescendentDeltas.end(); ++catit) @@ -959,6 +1642,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) { @@ -967,6 +1651,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 @@ -991,6 +1682,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<LLViewerInventoryItem> 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) @@ -1003,6 +1712,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 @@ -1063,6 +1779,8 @@ void AISUpdate::doUpdate() } } + checkTimeout(); + gInventory.notifyObservers(); } diff --git a/indra/newview/llaisapi.h b/indra/newview/llaisapi.h index 856f3fc180..0fdf4a0b74 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<void(const LLUUID &invItem)> 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,24 +110,30 @@ 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); 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<LLUUID,S32> uuid_int_map_t; uuid_int_map_t mCatDescendentDeltas; @@ -113,6 +142,7 @@ private: typedef std::map<LLUUID,LLPointer<LLViewerInventoryItem> > deferred_item_map_t; deferred_item_map_t mItemsCreated; + deferred_item_map_t mItemsLost; deferred_item_map_t mItemsUpdated; typedef std::map<LLUUID,LLPointer<LLViewerInventoryCategory> > deferred_category_map_t; deferred_category_map_t mCategoriesCreated; @@ -123,6 +153,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 d0fad07f1c..8010b84c20 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" @@ -590,6 +591,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 { @@ -1706,12 +1772,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, @@ -2414,6 +2486,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 @@ -2730,22 +2835,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); - - // 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(); + 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)); + + // BAP fixes a lag in display of created dir. + gInventory.notifyObservers(); + }, + cat->getThumbnailUUID() + ); } else { @@ -3203,7 +3315,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; @@ -3714,7 +3826,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 @@ -3986,26 +4098,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() @@ -4338,6 +4439,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) @@ -4354,13 +4522,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() @@ -4372,11 +4538,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); @@ -4401,18 +4569,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 cf953d21ac..43839e47a6 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -338,7 +338,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 1de0ed3f93..3991b069e6 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -216,7 +216,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" @@ -1537,7 +1537,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 1feefc3ef8..d3fce306bc 100644 --- a/indra/newview/llattachmentsmgr.cpp +++ b/indra/newview/llattachmentsmgr.cpp @@ -240,6 +240,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 3e450e6dec..313339f131 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -730,39 +730,55 @@ namespace action_give_inventory /** * Checks My Inventory visibility. */ + static bool is_give_inventory_acceptable_ids(const std::set<LLUUID> inventory_selected_uuids) + { + if (inventory_selected_uuids.empty()) return false; // nothing selected + + bool acceptable = false; + std::set<LLUUID>::const_iterator it = inventory_selected_uuids.begin(); + const std::set<LLUUID>::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<LLUUID> inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); - if (inventory_selected_uuids.empty()) return false; // nothing selected - - bool acceptable = false; - std::set<LLUUID>::const_iterator it = inventory_selected_uuids.begin(); - const std::set<LLUUID>::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; - } + std::set<LLUUID> 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 + } + } - // 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<LLUUID>& inventory_selected_uuids , std::string& items_string) @@ -890,46 +906,65 @@ 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<LLAvatarName> avatar_names, LLInventoryPanel* panel = NULL) - { - llassert(avatar_names.size() == avatar_uuids.size()); - const std::set<LLUUID> inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); - if (inventory_selected_uuids.empty()) - { - return; - } + static void give_inventory_ids(const uuid_vec_t& avatar_uuids, const std::vector<LLAvatarName> avatar_names, const uuid_set_t inventory_selected_uuids) + { + llassert(avatar_names.size() == avatar_uuids.size()); - std::string residents; - LLAvatarActions::buildResidentsString(avatar_names, residents, true); + if (inventory_selected_uuids.empty()) + { + return; + } - std::string items; - build_items_string(inventory_selected_uuids, items); + std::string residents; + LLAvatarActions::buildResidentsString(avatar_names, residents, true); - int folders_count = 0; - std::set<LLUUID>::const_iterator it = inventory_selected_uuids.begin(); + std::string items; + build_items_string(inventory_selected_uuids, items); - //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++; - } - } + int folders_count = 0; + std::set<LLUUID>::const_iterator it = inventory_selected_uuids.begin(); - // 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)); - } + //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)); + } + + static void give_inventory(const uuid_vec_t& avatar_uuids, const std::vector<LLAvatarName> avatar_names, LLInventoryPanel* panel = NULL) + { + llassert(avatar_names.size() == avatar_uuids.size()); + std::set<LLUUID> 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 @@ -1037,6 +1072,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 86183cc119..8a0f40dd52 100644 --- a/indra/newview/llavataractions.h +++ b/indra/newview/llavataractions.h @@ -133,6 +133,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 7c6980a7e6..3f607d434e 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -111,6 +111,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; } @@ -249,7 +250,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..780130039b --- /dev/null +++ b/indra/newview/llfloaterchangeitemthumbnail.cpp @@ -0,0 +1,956 @@ +/** + * @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" + + +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<std::string>& 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<std::string>& 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<LLUICtrl>("item_name"); + mItemTypeIcon = getChild<LLIconCtrl>("item_type_icon"); + mThumbnailCtrl = getChild<LLThumbnailCtrl>("item_thumbnail"); + mToolTipTextBox = getChild<LLTextBox>("tooltip_text"); + + LLSD tooltip_text; + mToolTipTextBox->setValue(tooltip_text); + + LLButton *upload_local = getChild<LLButton>("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<LLButton>("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<LLButton>("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<LLButton>("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<LLButton>("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<LLButton>("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<LLUUID>& mChangedItemIDs = gInventory.getChangedIDs(); + std::set<LLUUID>::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<LLViewerInventoryItem*>(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<LLViewerInventoryCategory*>(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<LLUUID> 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<LLFloater> 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<LLFloater> mFloaterHandle; + bool mSilent; + // Keep image reference to prevent deletion on timeout + LLPointer<LLViewerFetchedTexture> mTexturep; +}; + +void LLFloaterChangeItemThumbnail::assignAndValidateAsset(const LLUUID &asset_id, bool silent) +{ + LLPointer<LLViewerFetchedTexture> 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<LLViewerFetchedTexture> 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<LLFloaterChangeItemThumbnail*>(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, + "SELECT PHOTO", + PERM_NONE, + PERM_NONE, + PERM_NONE, + FALSE, + NULL); + + mPickerHandle = floaterp->getHandle(); + + LLFloaterTexturePicker* texture_floaterp = dynamic_cast<LLFloaterTexturePicker*>(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<LLViewerFetchedTexture> 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<LLViewerInventoryCategory*>(obj); + if (view_folder) + { + update_inventory_category(object_id, updates, NULL); + } + LLViewerInventoryItem* view_item = dynamic_cast<LLViewerInventoryItem*>(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<LLFloater> 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<LLFloater> mPickerHandle; + LLHandle<LLFloater> 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 97b022699f..f576ce7a76 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<LLCredential> 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<LLCredential> cred = gSecAPIHandler->loadCredential(grid); if (cred.notNull() && cred->userID() == userid) diff --git a/indra/newview/llfloatergesture.cpp b/indra/newview/llfloatergesture.cpp index d17889bed1..f29046c513 100644 --- a/indra/newview/llfloatergesture.cpp +++ b/indra/newview/llfloatergesture.cpp @@ -211,7 +211,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<LLButton>("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 8ee7a72055..b42c49c607 100644 --- a/indra/newview/llfloaterlinkreplace.cpp +++ b/indra/newview/llfloaterlinkreplace.cpp @@ -335,8 +335,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 e755e9924c..71b3b16809 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" ///---------------------------------------------------------------------------- /// LLPanelMarketplaceListings @@ -227,18 +230,31 @@ void LLPanelMarketplaceListings::onTabChange() void LLPanelMarketplaceListings::onAddButtonClicked() { - // Find active panel - LLInventoryPanel* panel = (LLInventoryPanel*)getChild<LLTabContainer>("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<LLPanel> 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<LLTabContainer>("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + gInventory.notifyObservers(); + panel->setSelectionByID(new_cat_id, TRUE); + panel->getRootFolder()->setNeedsAutoRename(TRUE); + } } + ); } void LLPanelMarketplaceListings::onAuditButtonClicked() @@ -359,6 +375,7 @@ LLFloaterMarketplaceListings::LLFloaterMarketplaceListings(const LLSD& key) , mInventoryTitle(NULL) , mPanelListings(NULL) , mPanelListingsSet(false) +, mRootFolderCreating(false) { } @@ -431,7 +448,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(); } } @@ -444,15 +461,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<LLFloaterMarketplaceListings>("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)) @@ -540,6 +592,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)) ) @@ -843,14 +900,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 @@ -954,18 +1014,44 @@ LLFloaterItemProperties::~LLFloaterItemProperties() BOOL LLFloaterItemProperties::postBuild() { - // On the standalone properties floater, we have no need for a back button... - LLSidepanelItemInfo* panel = getChild<LLSidepanelItemInfo>("item_panel"); - LLButton* back_btn = panel->getChild<LLButton>("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<LLSidepanelItemInfo>("item_panel"); - panel->setItemID(key["id"].asUUID()); + LLPanel* panel = findChild<LLPanel>("sidepanel"); + + LLSidepanelItemInfo* item_panel = dynamic_cast<LLSidepanelItemInfo*>(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<LLSidepanelTaskInfo*>(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(); +} diff --git a/indra/newview/llfloatermarketplacelistings.h b/indra/newview/llfloatermarketplacelistings.h index ffc098e28a..085e517a9d 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; @@ -139,6 +140,7 @@ private: LLTextBox * mInventoryTitle; LLUUID mRootFolderId; + bool mRootFolderCreating; LLPanelMarketplaceListings * mPanelListings; bool mPanelListingsSet; }; @@ -223,4 +225,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<LLButton>("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<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature)); + getChild<LLUICtrl>(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<LLFloaterView*>(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 a682064dad..d3ab22f792 100644 --- a/indra/newview/llfloateropenobject.cpp +++ b/indra/newview/llfloateropenobject.cpp @@ -164,34 +164,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 @@ -206,9 +184,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<LLButton>("ok_btn")->setClickedCallback(boost::bind(&LLFloaterOutfitPhotoPreview::onOkBtn, this)); - getChild<LLButton>("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<LLUICtrl>("dimensions")->setTextArg("[WIDTH]", llformat("%d", mImage->getFullWidth())); - getChild<LLUICtrl>("dimensions")->setTextArg("[HEIGHT]", llformat("%d", mImage->getFullHeight())); - - if ((mImage->getFullWidth() <= MAX_OUTFIT_PHOTO_WIDTH) && (mImage->getFullHeight() <= MAX_OUTFIT_PHOTO_HEIGHT)) - { - getChild<LLButton>("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<LLUICtrl>("notification")->setValue(label); - getChild<LLUICtrl>("notification")->setColor(LLColor4::yellow); - getChild<LLButton>("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<const LLViewerInventoryItem*>(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<LLUICtrl>("notification")->setValue( getString("photo_confirmation")); - getChild<LLUICtrl>("notification")->setTextArg("[OUTFIT]", outfit_folder->getName()); - getChild<LLUICtrl>("notification")->setColor(LLColor4::white); - } - -} - -void LLFloaterOutfitPhotoPreview::onOkBtn() -{ - if(mOutfitID.notNull() && getItem()) - { - LLAppearanceMgr::instance().removeOutfitPhoto(mOutfitID); - LLPointer<LLInventoryCallback> cb = NULL; - link_inventory_object(mOutfitID, LLConstPointer<LLInventoryObject>(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<LLViewerFetchedTexture> 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/llfloaterproperties.cpp b/indra/newview/llfloaterproperties.cpp deleted file mode 100644 index 64ad40f419..0000000000 --- a/indra/newview/llfloaterproperties.cpp +++ /dev/null @@ -1,891 +0,0 @@ -/** - * @file llfloaterproperties.cpp - * @brief A floater which shows an inventory item's properties. - * - * $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 "llfloaterproperties.h" - -#include <algorithm> -#include <functional> -#include "llcachename.h" -#include "llavatarnamecache.h" -#include "lldbstrings.h" -#include "llfloaterreg.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llavataractions.h" -#include "llinventorydefines.h" -#include "llinventoryobserver.h" -#include "llinventorymodel.h" -#include "lllineeditor.h" -//#include "llspinctrl.h" -#include "llradiogroup.h" -#include "llresmgr.h" -#include "roles_constants.h" -#include "llselectmgr.h" -#include "lltextbox.h" -#include "lltrans.h" -#include "lluiconstants.h" -#include "llviewerinventory.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llgroupactions.h" - -#include "lluictrlfactory.h" - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLPropertiesObserver -// -// helper class to watch the inventory. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -// Ugh. This can't be a singleton because it needs to remove itself -// from the inventory observer list when destroyed, which could -// happen after gInventory has already been destroyed if a singleton. -// Instead, do our own ref counting and create / destroy it as needed -class LLPropertiesObserver : public LLInventoryObserver -{ -public: - LLPropertiesObserver(LLFloaterProperties* floater) - : mFloater(floater) - { - gInventory.addObserver(this); - } - virtual ~LLPropertiesObserver() - { - gInventory.removeObserver(this); - } - virtual void changed(U32 mask); -private: - LLFloaterProperties* mFloater; // Not a handle because LLFloaterProperties is managing LLPropertiesObserver -}; - -void LLPropertiesObserver::changed(U32 mask) -{ - // if there's a change we're interested in. - if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) - { - mFloater->dirty(); - } -} - - - -///---------------------------------------------------------------------------- -/// Class LLFloaterProperties -///---------------------------------------------------------------------------- - -// Default constructor -LLFloaterProperties::LLFloaterProperties(const LLUUID& item_id) - : LLFloater(mItemID), - mItemID(item_id), - mDirty(TRUE) -{ - mPropertiesObserver = new LLPropertiesObserver(this); -} - -// Destroys the object -LLFloaterProperties::~LLFloaterProperties() -{ - delete mPropertiesObserver; - mPropertiesObserver = NULL; -} - -// virtual -BOOL LLFloaterProperties::postBuild() -{ - // build the UI - // item name & description - getChild<LLLineEditor>("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - getChild<LLUICtrl>("LabelItemName")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitName,this)); - getChild<LLLineEditor>("LabelItemDesc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - getChild<LLUICtrl>("LabelItemDesc")->setCommitCallback(boost::bind(&LLFloaterProperties:: onCommitDescription, this)); - // Creator information - getChild<LLUICtrl>("BtnCreator")->setCommitCallback(boost::bind(&LLFloaterProperties::onClickCreator,this)); - // owner information - getChild<LLUICtrl>("BtnOwner")->setCommitCallback(boost::bind(&LLFloaterProperties::onClickOwner,this)); - // acquired date - // owner permissions - // Permissions debug text - // group permissions - getChild<LLUICtrl>("CheckShareWithGroup")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitPermissions, this)); - // everyone permissions - getChild<LLUICtrl>("CheckEveryoneCopy")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitPermissions, this)); - // next owner permissions - getChild<LLUICtrl>("CheckNextOwnerModify")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitPermissions, this)); - getChild<LLUICtrl>("CheckNextOwnerCopy")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitPermissions, this)); - getChild<LLUICtrl>("CheckNextOwnerTransfer")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitPermissions, this)); - // Mark for sale or not, and sale info - getChild<LLUICtrl>("CheckPurchase")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitSaleInfo, this)); - getChild<LLUICtrl>("ComboBoxSaleType")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitSaleType, this)); - // "Price" label for edit - getChild<LLUICtrl>("Edit Cost")->setCommitCallback(boost::bind(&LLFloaterProperties::onCommitSaleInfo, this)); - // The UI has been built, now fill in all the values - refresh(); - - return TRUE; -} - -// virtual -void LLFloaterProperties::onOpen(const LLSD& key) -{ - refresh(); -} - -void LLFloaterProperties::refresh() -{ - LLInventoryItem* item = findItem(); - if(item) - { - refreshFromItem(item); - } - else - { - //RN: it is possible that the container object is in the middle of an inventory refresh - // causing findItem() to fail, so just temporarily disable everything - - mDirty = TRUE; - - const char* enableNames[]={ - "LabelItemName", - "LabelItemDesc", - "LabelCreatorName", - "BtnCreator", - "LabelOwnerName", - "BtnOwner", - "CheckOwnerModify", - "CheckOwnerCopy", - "CheckOwnerTransfer", - "CheckShareWithGroup", - "CheckEveryoneCopy", - "CheckNextOwnerModify", - "CheckNextOwnerCopy", - "CheckNextOwnerTransfer", - "CheckPurchase", - "ComboBoxSaleType", - "Edit Cost" - }; - for(size_t t=0; t<LL_ARRAY_SIZE(enableNames); ++t) - { - getChildView(enableNames[t])->setEnabled(false); - } - const char* hideNames[]={ - "BaseMaskDebug", - "OwnerMaskDebug", - "GroupMaskDebug", - "EveryoneMaskDebug", - "NextMaskDebug" - }; - for(size_t t=0; t<LL_ARRAY_SIZE(hideNames); ++t) - { - getChildView(hideNames[t])->setVisible(false); - } - } -} - -void LLFloaterProperties::draw() -{ - if (mDirty) - { - // RN: clear dirty first because refresh can set dirty to TRUE - mDirty = FALSE; - refresh(); - } - - LLFloater::draw(); -} - -void LLFloaterProperties::refreshFromItem(LLInventoryItem* item) -{ - //////////////////////// - // PERMISSIONS LOOKUP // - //////////////////////// - - // do not enable the UI for incomplete items. - LLViewerInventoryItem* i = (LLViewerInventoryItem*)item; - BOOL is_complete = i->isFinished(); - const BOOL cannot_restrict_permissions = LLInventoryType::cannotRestrictPermissions(i->getInventoryType()); - const BOOL is_calling_card = (i->getInventoryType() == LLInventoryType::IT_CALLINGCARD); - const LLPermissions& perm = item->getPermissions(); - const BOOL can_agent_manipulate = gAgent.allowOperation(PERM_OWNER, perm, - GP_OBJECT_MANIPULATE); - const BOOL can_agent_sell = gAgent.allowOperation(PERM_OWNER, perm, - GP_OBJECT_SET_SALE) && - !cannot_restrict_permissions; - const BOOL is_link = i->getIsLinkType(); - - // You need permission to modify the object to modify an inventory - // item in it. - LLViewerObject* object = NULL; - if(!mObjectID.isNull()) object = gObjectList.findObject(mObjectID); - BOOL is_obj_modify = TRUE; - if(object) - { - is_obj_modify = object->permOwnerModify(); - } - - ////////////////////// - // ITEM NAME & DESC // - ////////////////////// - BOOL is_modifiable = gAgent.allowOperation(PERM_MODIFY, perm, - GP_OBJECT_MANIPULATE) - && is_obj_modify && is_complete; - - getChildView("LabelItemNameTitle")->setEnabled(TRUE); - getChildView("LabelItemName")->setEnabled(is_modifiable && !is_calling_card); // for now, don't allow rename of calling cards - getChild<LLUICtrl>("LabelItemName")->setValue(item->getName()); - getChildView("LabelItemDescTitle")->setEnabled(TRUE); - getChildView("LabelItemDesc")->setEnabled(is_modifiable); - getChildView("IconLocked")->setVisible(!is_modifiable); - getChild<LLUICtrl>("LabelItemDesc")->setValue(item->getDescription()); - - ////////////////// - // CREATOR NAME // - ////////////////// - if(!gCacheName) return; - if(!gAgent.getRegion()) return; - - if (item->getCreatorUUID().notNull()) - { - LLAvatarName av_name; - LLAvatarNameCache::get(item->getCreatorUUID(), &av_name); - getChildView("BtnCreator")->setEnabled(TRUE); - getChildView("LabelCreatorTitle")->setEnabled(TRUE); - getChildView("LabelCreatorName")->setEnabled(TRUE); - getChild<LLUICtrl>("LabelCreatorName")->setValue(av_name.getUserName()); - } - else - { - getChildView("BtnCreator")->setEnabled(FALSE); - getChildView("LabelCreatorTitle")->setEnabled(FALSE); - getChildView("LabelCreatorName")->setEnabled(FALSE); - getChild<LLUICtrl>("LabelCreatorName")->setValue(getString("unknown")); - } - - //////////////// - // OWNER NAME // - //////////////// - if(perm.isOwned()) - { - std::string name; - if (perm.isGroupOwned()) - { - gCacheName->getGroupName(perm.getGroup(), name); - } - else - { - LLAvatarName av_name; - LLAvatarNameCache::get(perm.getOwner(), &av_name); - name = av_name.getUserName(); - } - getChildView("BtnOwner")->setEnabled(TRUE); - getChildView("LabelOwnerTitle")->setEnabled(TRUE); - getChildView("LabelOwnerName")->setEnabled(TRUE); - getChild<LLUICtrl>("LabelOwnerName")->setValue(name); - } - else - { - getChildView("BtnOwner")->setEnabled(FALSE); - getChildView("LabelOwnerTitle")->setEnabled(FALSE); - getChildView("LabelOwnerName")->setEnabled(FALSE); - getChild<LLUICtrl>("LabelOwnerName")->setValue(getString("public")); - } - - ////////////////// - // ACQUIRE DATE // - ////////////////// - - time_t time_utc = item->getCreationDate(); - if (0 == time_utc) - { - getChild<LLUICtrl>("LabelAcquiredDate")->setValue(getString("unknown")); - } - else - { - std::string timeStr = getString("acquiredDate"); - LLSD substitution; - substitution["datetime"] = (S32) time_utc; - LLStringUtil::format (timeStr, substitution); - getChild<LLUICtrl>("LabelAcquiredDate")->setValue(timeStr); - } - - /////////////////////// - // OWNER PERMISSIONS // - /////////////////////// - if(can_agent_manipulate) - { - getChild<LLUICtrl>("OwnerLabel")->setValue(getString("you_can")); - } - else - { - getChild<LLUICtrl>("OwnerLabel")->setValue(getString("owner_can")); - } - - U32 base_mask = perm.getMaskBase(); - U32 owner_mask = perm.getMaskOwner(); - U32 group_mask = perm.getMaskGroup(); - U32 everyone_mask = perm.getMaskEveryone(); - U32 next_owner_mask = perm.getMaskNextOwner(); - - getChildView("OwnerLabel")->setEnabled(TRUE); - getChildView("CheckOwnerModify")->setEnabled(FALSE); - getChild<LLUICtrl>("CheckOwnerModify")->setValue(LLSD((BOOL)(owner_mask & PERM_MODIFY))); - getChildView("CheckOwnerCopy")->setEnabled(FALSE); - getChild<LLUICtrl>("CheckOwnerCopy")->setValue(LLSD((BOOL)(owner_mask & PERM_COPY))); - getChildView("CheckOwnerTransfer")->setEnabled(FALSE); - getChild<LLUICtrl>("CheckOwnerTransfer")->setValue(LLSD((BOOL)(owner_mask & PERM_TRANSFER))); - - /////////////////////// - // DEBUG PERMISSIONS // - /////////////////////// - - if( gSavedSettings.getBOOL("DebugPermissions") ) - { - BOOL slam_perm = FALSE; - BOOL overwrite_group = FALSE; - BOOL overwrite_everyone = FALSE; - - if (item->getType() == LLAssetType::AT_OBJECT) - { - U32 flags = item->getFlags(); - slam_perm = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM; - overwrite_everyone = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; - overwrite_group = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; - } - - std::string perm_string; - - perm_string = "B: "; - perm_string += mask_to_string(base_mask); - getChild<LLUICtrl>("BaseMaskDebug")->setValue(perm_string); - getChildView("BaseMaskDebug")->setVisible(TRUE); - - perm_string = "O: "; - perm_string += mask_to_string(owner_mask); - getChild<LLUICtrl>("OwnerMaskDebug")->setValue(perm_string); - getChildView("OwnerMaskDebug")->setVisible(TRUE); - - perm_string = "G"; - perm_string += overwrite_group ? "*: " : ": "; - perm_string += mask_to_string(group_mask); - getChild<LLUICtrl>("GroupMaskDebug")->setValue(perm_string); - getChildView("GroupMaskDebug")->setVisible(TRUE); - - perm_string = "E"; - perm_string += overwrite_everyone ? "*: " : ": "; - perm_string += mask_to_string(everyone_mask); - getChild<LLUICtrl>("EveryoneMaskDebug")->setValue(perm_string); - getChildView("EveryoneMaskDebug")->setVisible(TRUE); - - perm_string = "N"; - perm_string += slam_perm ? "*: " : ": "; - perm_string += mask_to_string(next_owner_mask); - getChild<LLUICtrl>("NextMaskDebug")->setValue(perm_string); - getChildView("NextMaskDebug")->setVisible(TRUE); - } - else - { - getChildView("BaseMaskDebug")->setVisible(FALSE); - getChildView("OwnerMaskDebug")->setVisible(FALSE); - getChildView("GroupMaskDebug")->setVisible(FALSE); - getChildView("EveryoneMaskDebug")->setVisible(FALSE); - getChildView("NextMaskDebug")->setVisible(FALSE); - } - - ///////////// - // SHARING // - ///////////// - - // Check for ability to change values. - if (is_link || cannot_restrict_permissions) - { - getChildView("CheckShareWithGroup")->setEnabled(FALSE); - getChildView("CheckEveryoneCopy")->setEnabled(FALSE); - } - else if (is_obj_modify && can_agent_manipulate) - { - getChildView("CheckShareWithGroup")->setEnabled(TRUE); - getChildView("CheckEveryoneCopy")->setEnabled((owner_mask & PERM_COPY) && (owner_mask & PERM_TRANSFER)); - } - else - { - getChildView("CheckShareWithGroup")->setEnabled(FALSE); - getChildView("CheckEveryoneCopy")->setEnabled(FALSE); - } - - // Set values. - BOOL is_group_copy = (group_mask & PERM_COPY) ? TRUE : FALSE; - BOOL is_group_modify = (group_mask & PERM_MODIFY) ? TRUE : FALSE; - BOOL is_group_move = (group_mask & PERM_MOVE) ? TRUE : FALSE; - - if (is_group_copy && is_group_modify && is_group_move) - { - getChild<LLUICtrl>("CheckShareWithGroup")->setValue(LLSD((BOOL)TRUE)); - - LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"); - if(ctl) - { - ctl->setTentative(FALSE); - } - } - else if (!is_group_copy && !is_group_modify && !is_group_move) - { - getChild<LLUICtrl>("CheckShareWithGroup")->setValue(LLSD((BOOL)FALSE)); - LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"); - if(ctl) - { - ctl->setTentative(FALSE); - } - } - else - { - LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"); - if(ctl) - { - ctl->setTentative(TRUE); - ctl->set(TRUE); - } - } - - getChild<LLUICtrl>("CheckEveryoneCopy")->setValue(LLSD((BOOL)(everyone_mask & PERM_COPY))); - - /////////////// - // SALE INFO // - /////////////// - - const LLSaleInfo& sale_info = item->getSaleInfo(); - BOOL is_for_sale = sale_info.isForSale(); - LLComboBox* combo_sale_type = getChild<LLComboBox>("ComboBoxSaleType"); - LLUICtrl* edit_cost = getChild<LLUICtrl>("Edit Cost"); - - // Check for ability to change values. - if (is_obj_modify && can_agent_sell - && gAgent.allowOperation(PERM_TRANSFER, perm, GP_OBJECT_MANIPULATE)) - { - getChildView("CheckPurchase")->setEnabled(is_complete); - - getChildView("NextOwnerLabel")->setEnabled(TRUE); - getChildView("CheckNextOwnerModify")->setEnabled((base_mask & PERM_MODIFY) && !cannot_restrict_permissions); - getChildView("CheckNextOwnerCopy")->setEnabled((base_mask & PERM_COPY) && !cannot_restrict_permissions); - getChildView("CheckNextOwnerTransfer")->setEnabled((next_owner_mask & PERM_COPY) && !cannot_restrict_permissions); - - combo_sale_type->setEnabled(is_complete && is_for_sale); - edit_cost->setEnabled(is_complete && is_for_sale); - } - else - { - getChildView("CheckPurchase")->setEnabled(FALSE); - - getChildView("NextOwnerLabel")->setEnabled(FALSE); - getChildView("CheckNextOwnerModify")->setEnabled(FALSE); - getChildView("CheckNextOwnerCopy")->setEnabled(FALSE); - getChildView("CheckNextOwnerTransfer")->setEnabled(FALSE); - - combo_sale_type->setEnabled(FALSE); - edit_cost->setEnabled(FALSE); - } - - // Set values. - getChild<LLUICtrl>("CheckPurchase")->setValue(is_for_sale); - getChild<LLUICtrl>("CheckNextOwnerModify")->setValue(LLSD(BOOL(next_owner_mask & PERM_MODIFY))); - getChild<LLUICtrl>("CheckNextOwnerCopy")->setValue(LLSD(BOOL(next_owner_mask & PERM_COPY))); - getChild<LLUICtrl>("CheckNextOwnerTransfer")->setValue(LLSD(BOOL(next_owner_mask & PERM_TRANSFER))); - - if (is_for_sale) - { - S32 numerical_price; - numerical_price = sale_info.getSalePrice(); - edit_cost->setValue(llformat("%d",numerical_price)); - combo_sale_type->setValue(sale_info.getSaleType()); - } - else - { - edit_cost->setValue(llformat("%d",0)); - combo_sale_type->setValue(LLSaleInfo::FS_COPY); - } -} - -void LLFloaterProperties::onClickCreator() -{ - LLInventoryItem* item = findItem(); - if(!item) return; - if(!item->getCreatorUUID().isNull()) - { - LLAvatarActions::showProfile(item->getCreatorUUID()); - } -} - -// static -void LLFloaterProperties::onClickOwner() -{ - LLInventoryItem* item = findItem(); - if(!item) return; - if(item->getPermissions().isGroupOwned()) - { - LLGroupActions::show(item->getPermissions().getGroup()); - } - else - { - LLAvatarActions::showProfile(item->getPermissions().getOwner()); - } -} - -// static -void LLFloaterProperties::onCommitName() -{ - //LL_INFOS() << "LLFloaterProperties::onCommitName()" << LL_ENDL; - LLViewerInventoryItem* item = (LLViewerInventoryItem*)findItem(); - if(!item) - { - return; - } - LLLineEditor* labelItemName = getChild<LLLineEditor>("LabelItemName"); - - if(labelItemName&& - (item->getName() != labelItemName->getText()) && - (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) ) - { - LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); - new_item->rename(labelItemName->getText()); - if(mObjectID.isNull()) - { - new_item->updateServer(FALSE); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - LLViewerObject* object = gObjectList.findObject(mObjectID); - if(object) - { - object->updateInventory( - new_item, - TASK_INVENTORY_ITEM_KEY, - false); - } - } - } -} - -void LLFloaterProperties::onCommitDescription() -{ - //LL_INFOS() << "LLFloaterProperties::onCommitDescription()" << LL_ENDL; - LLViewerInventoryItem* item = (LLViewerInventoryItem*)findItem(); - if(!item) return; - - LLLineEditor* labelItemDesc = getChild<LLLineEditor>("LabelItemDesc"); - if(!labelItemDesc) - { - return; - } - if((item->getDescription() != labelItemDesc->getText()) && - (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE))) - { - LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); - - new_item->setDescription(labelItemDesc->getText()); - if(mObjectID.isNull()) - { - new_item->updateServer(FALSE); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - LLViewerObject* object = gObjectList.findObject(mObjectID); - if(object) - { - object->updateInventory( - new_item, - TASK_INVENTORY_ITEM_KEY, - false); - } - } - } -} - -// static -void LLFloaterProperties::onCommitPermissions() -{ - //LL_INFOS() << "LLFloaterProperties::onCommitPermissions()" << LL_ENDL; - LLViewerInventoryItem* item = (LLViewerInventoryItem*)findItem(); - if(!item) return; - LLPermissions perm(item->getPermissions()); - - - LLCheckBoxCtrl* CheckShareWithGroup = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"); - - if(CheckShareWithGroup) - { - perm.setGroupBits(gAgent.getID(), gAgent.getGroupID(), - CheckShareWithGroup->get(), - PERM_MODIFY | PERM_MOVE | PERM_COPY); - } - LLCheckBoxCtrl* CheckEveryoneCopy = getChild<LLCheckBoxCtrl>("CheckEveryoneCopy"); - if(CheckEveryoneCopy) - { - perm.setEveryoneBits(gAgent.getID(), gAgent.getGroupID(), - CheckEveryoneCopy->get(), PERM_COPY); - } - - LLCheckBoxCtrl* CheckNextOwnerModify = getChild<LLCheckBoxCtrl>("CheckNextOwnerModify"); - if(CheckNextOwnerModify) - { - perm.setNextOwnerBits(gAgent.getID(), gAgent.getGroupID(), - CheckNextOwnerModify->get(), PERM_MODIFY); - } - LLCheckBoxCtrl* CheckNextOwnerCopy = getChild<LLCheckBoxCtrl>("CheckNextOwnerCopy"); - if(CheckNextOwnerCopy) - { - perm.setNextOwnerBits(gAgent.getID(), gAgent.getGroupID(), - CheckNextOwnerCopy->get(), PERM_COPY); - } - LLCheckBoxCtrl* CheckNextOwnerTransfer = getChild<LLCheckBoxCtrl>("CheckNextOwnerTransfer"); - if(CheckNextOwnerTransfer) - { - perm.setNextOwnerBits(gAgent.getID(), gAgent.getGroupID(), - CheckNextOwnerTransfer->get(), PERM_TRANSFER); - } - if(perm != item->getPermissions() - && item->isFinished()) - { - LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); - new_item->setPermissions(perm); - U32 flags = new_item->getFlags(); - // If next owner permissions have changed (and this is an object) - // then set the slam permissions flag so that they are applied on rez. - if((perm.getMaskNextOwner()!=item->getPermissions().getMaskNextOwner()) - && (item->getType() == LLAssetType::AT_OBJECT)) - { - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM; - } - // If everyone permissions have changed (and this is an object) - // then set the overwrite everyone permissions flag so they - // are applied on rez. - if ((perm.getMaskEveryone()!=item->getPermissions().getMaskEveryone()) - && (item->getType() == LLAssetType::AT_OBJECT)) - { - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; - } - // If group permissions have changed (and this is an object) - // then set the overwrite group permissions flag so they - // are applied on rez. - if ((perm.getMaskGroup()!=item->getPermissions().getMaskGroup()) - && (item->getType() == LLAssetType::AT_OBJECT)) - { - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; - } - new_item->setFlags(flags); - if(mObjectID.isNull()) - { - new_item->updateServer(FALSE); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - LLViewerObject* object = gObjectList.findObject(mObjectID); - if(object) - { - object->updateInventory( - new_item, - TASK_INVENTORY_ITEM_KEY, - false); - } - } - } - else - { - // need to make sure we don't just follow the click - refresh(); - } -} - -// static -void LLFloaterProperties::onCommitSaleInfo() -{ - //LL_INFOS() << "LLFloaterProperties::onCommitSaleInfo()" << LL_ENDL; - updateSaleInfo(); -} - -// static -void LLFloaterProperties::onCommitSaleType() -{ - //LL_INFOS() << "LLFloaterProperties::onCommitSaleType()" << LL_ENDL; - updateSaleInfo(); -} - -void LLFloaterProperties::updateSaleInfo() -{ - LLViewerInventoryItem* item = (LLViewerInventoryItem*)findItem(); - if(!item) return; - LLSaleInfo sale_info(item->getSaleInfo()); - if(!gAgent.allowOperation(PERM_TRANSFER, item->getPermissions(), GP_OBJECT_SET_SALE)) - { - getChild<LLUICtrl>("CheckPurchase")->setValue(LLSD((BOOL)FALSE)); - } - - if((BOOL)getChild<LLUICtrl>("CheckPurchase")->getValue()) - { - // turn on sale info - LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_COPY; - - LLComboBox* combo_sale_type = getChild<LLComboBox>("ComboBoxSaleType"); - if (combo_sale_type) - { - sale_type = static_cast<LLSaleInfo::EForSale>(combo_sale_type->getValue().asInteger()); - } - - if (sale_type == LLSaleInfo::FS_COPY - && !gAgent.allowOperation(PERM_COPY, item->getPermissions(), - GP_OBJECT_SET_SALE)) - { - sale_type = LLSaleInfo::FS_ORIGINAL; - } - - - - S32 price = -1; - price = getChild<LLUICtrl>("Edit Cost")->getValue().asInteger();; - - // Invalid data - turn off the sale - if (price < 0) - { - sale_type = LLSaleInfo::FS_NOT; - price = 0; - } - - sale_info.setSaleType(sale_type); - sale_info.setSalePrice(price); - } - else - { - sale_info.setSaleType(LLSaleInfo::FS_NOT); - } - if(sale_info != item->getSaleInfo() - && item->isFinished()) - { - LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); - - // Force an update on the sale price at rez - if (item->getType() == LLAssetType::AT_OBJECT) - { - U32 flags = new_item->getFlags(); - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_SALE; - new_item->setFlags(flags); - } - - new_item->setSaleInfo(sale_info); - if(mObjectID.isNull()) - { - // This is in the agent's inventory. - new_item->updateServer(FALSE); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - // This is in an object's contents. - LLViewerObject* object = gObjectList.findObject(mObjectID); - if(object) - { - object->updateInventory( - new_item, - TASK_INVENTORY_ITEM_KEY, - false); - } - } - } - else - { - // need to make sure we don't just follow the click - refresh(); - } -} - -LLInventoryItem* LLFloaterProperties::findItem() const -{ - LLInventoryItem* item = NULL; - if(mObjectID.isNull()) - { - // it is in agent inventory - item = gInventory.getItem(mItemID); - } - else - { - LLViewerObject* object = gObjectList.findObject(mObjectID); - if(object) - { - item = (LLInventoryItem*)object->getInventoryObject(mItemID); - } - } - return item; -} - -//static -void LLFloaterProperties::dirtyAll() -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("properties"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); - iter != inst_list.end(); ++iter) - { - LLFloaterProperties* floater = dynamic_cast<LLFloaterProperties*>(*iter); - llassert(floater); // else cast failed - wrong type D: - if (floater) - { - floater->dirty(); - } - } -} - -///---------------------------------------------------------------------------- -/// LLMultiProperties -///---------------------------------------------------------------------------- - -LLMultiProperties::LLMultiProperties() - : LLMultiFloater(LLSD()) -{ - // start with a small rect in the top-left corner ; will get resized - LLRect rect; - rect.setLeftTopAndSize(0, gViewerWindow->getWindowHeightScaled(), 20, 20); - setRect(rect); - LLFloater* last_floater = LLFloaterReg::getLastFloaterInGroup("properties"); - if (last_floater) - { - stackWith(*last_floater); - } - setTitle(LLTrans::getString("MultiPropertiesTitle")); - buildTabContainer(); -} - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- diff --git a/indra/newview/llfloaterproperties.h b/indra/newview/llfloaterproperties.h deleted file mode 100644 index aa3fcec337..0000000000 --- a/indra/newview/llfloaterproperties.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @file llfloaterproperties.h - * @brief A floater which shows an inventory item's properties. - * - * $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_LLFLOATERPROPERTIES_H -#define LL_LLFLOATERPROPERTIES_H - -#include <map> -#include "llmultifloater.h" -#include "lliconctrl.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterProperties -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLButton; -class LLCheckBoxCtrl; -class LLInventoryItem; -class LLLineEditor; -class LLRadioGroup; -class LLTextBox; - -class LLPropertiesObserver; - -class LLFloaterProperties : public LLFloater -{ -public: - LLFloaterProperties(const LLUUID& item_id); - /*virtual*/ ~LLFloaterProperties(); - - /*virtual*/ BOOL postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - void setObjectID(const LLUUID& object_id) { mObjectID = object_id; } - - void dirty() { mDirty = TRUE; } - void refresh(); - - static void dirtyAll(); - -protected: - // ui callbacks - void onClickCreator(); - void onClickOwner(); - void onCommitName(); - void onCommitDescription(); - void onCommitPermissions(); - void onCommitSaleInfo(); - void onCommitSaleType(); - void updateSaleInfo(); - - LLInventoryItem* findItem() const; - - void refreshFromItem(LLInventoryItem* item); - virtual void draw(); - -protected: - // The item id of the inventory item in question. - LLUUID mItemID; - - // mObjectID will have a value if it is associated with a task in - // the world, and will be == LLUUID::null if it's in the agent - // inventory. - LLUUID mObjectID; - - BOOL mDirty; - - LLPropertiesObserver* mPropertiesObserver; -}; - -class LLMultiProperties : public LLMultiFloater -{ -public: - LLMultiProperties(); -}; - -#endif // LL_LLFLOATERPROPERTIES_H diff --git a/indra/newview/llfloatersidepanelcontainer.cpp b/indra/newview/llfloatersidepanelcontainer.cpp index dd7ce40e97..a875d33679 100644 --- a/indra/newview/llfloatersidepanelcontainer.cpp +++ b/indra/newview/llfloatersidepanelcontainer.cpp @@ -90,6 +90,29 @@ 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"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_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 20baf28184..5e7e755d1f 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(); + static void showPanel(const std::string& floater_name, const LLSD& key); static void showPanel(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<LLSimpleOutfitSnapshotFloaterView> 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<LLUICtrl>("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<LLUICtrl>("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<LLFloaterSimpleOutfitSnapshot>("simple_outfit_snapshot"); -} - -// static -LLFloaterSimpleOutfitSnapshot* LLFloaterSimpleOutfitSnapshot::getInstance() -{ - return LLFloaterReg::getTypedInstance<LLFloaterSimpleOutfitSnapshot>("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<LLUICtrl>("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<F32> 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<LLImageRaw> 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<LLFloaterSimpleSnapshot*>(*iter); + if (floater) + { + floater->impl->updateLivePreview(); + } + } +} + + +// static +LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::findInstance(const LLSD &key) +{ + return LLFloaterReg::findTypedInstance<LLFloaterSimpleSnapshot>("simple_snapshot", key); +} + +// static +LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::getInstance(const LLSD &key) +{ + return LLFloaterReg::getTypedInstance<LLFloaterSimpleSnapshot>("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 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<LLImageRaw> 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 e395da7f1e..ce28915d93 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 5329f10612..c52d0be213 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -662,9 +662,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<LLTextureToolTip>(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<LLTextureToolTip>(p); + } + } + } + } + + // No or more than one texture found => show default tooltip + return LLUICtrlFactory::create<LLToolTip>(p); + } + default: + return LLUICtrlFactory::create<LLToolTip>(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<LLViewerFetchedTexture> 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<LLTexturePreviewView>(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 db347f7096..73005d6903 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" @@ -90,29 +89,13 @@ void copy_slurl_to_clipboard_callback_inv(const std::string& slurl); -typedef std::pair<LLUUID, LLUUID> two_uuids_t; -typedef std::list<two_uuids_t> 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<LLMoveInv>); 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); static bool check_category(LLInventoryModel* model, const LLUUID& cat_id, LLInventoryPanel* active_panel, @@ -138,6 +121,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 { @@ -173,6 +162,65 @@ public: } }; +class LLPasteIntoFolderCallback: public LLInventoryCallback +{ +public: + LLPasteIntoFolderCallback(LLHandle<LLInventoryPanel>& 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<LLInventoryPanel> mInventoryPanel; + std::vector<LLUUID> mChangedIds; +}; + // +=================================================+ // | LLInvFVBridge | // +=================================================+ @@ -212,54 +260,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; - if (LLAvatarNameCache::get(item->getCreatorUUID(), &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())) - { - std::string uuid = item->getAssetUUID().asString(); - LLStringUtil::toUpper(uuid); - return uuid; - } - } - return LLStringUtil::null; + return get_searchable_UUID(getInventoryModel(), mUUID); } // Folders have full perms @@ -327,7 +338,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) || @@ -407,6 +418,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<LLInventorySingleFolderPanel*>(mInventoryPanel.get()); + if (!panel || !getInventoryModel() || mUUID.isNull()) + { + return; + } + + panel->changeFolderRoot(mUUID); + } + + } +} + void LLInvFVBridge::removeBatch(std::vector<LLFolderViewModelItem*>& batch) { // Deactivate gestures when moving them into Trash @@ -800,6 +837,7 @@ 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) { @@ -811,7 +849,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, 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")); @@ -845,7 +883,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")); if (!isItemRenameable() || ((flags & FIRST_SELECTED_ITEM) == 0)) @@ -853,14 +891,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()); @@ -873,6 +917,8 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } } + if(!single_folder_root) + { items.push_back(std::string("Cut")); if (!isItemMovable() || !isItemRemovable()) { @@ -894,6 +940,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } } } + } } } @@ -919,16 +966,12 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, items.push_back(std::string("Paste Separator")); - addDeleteContextMenuOptions(items, disabled_items); - - // If multiple items are selected, disable properties (if it exists). - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Properties")); - } + if(!single_folder_root) + { + addDeleteContextMenuOptions(items, disabled_items); + } - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); - if (active_panel && (active_panel->getName() != "All Items")) + if (!isPanelActive("All Items") && !isPanelActive("comb_single_folder_inv")) { items.push_back(std::string("Show in Main Panel")); } @@ -1019,7 +1062,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")); } @@ -1250,6 +1293,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(); @@ -1280,7 +1333,7 @@ BOOL LLInvFVBridge::isCOFFolder() 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()) { @@ -1292,7 +1345,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()) { @@ -1592,7 +1645,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); @@ -1691,6 +1744,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 @@ -1745,7 +1804,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)); } @@ -1959,9 +2018,9 @@ std::string LLItemBridge::getLabelSuffix() const { // String table is loaded before login screen and inventory items are // loaded after login, so LLTrans should be ready. - 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 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 LINK = LLTrans::getString("link"); static std::string BROKEN_LINK = LLTrans::getString("broken_link"); std::string suffix; @@ -1982,17 +2041,20 @@ std::string LLItemBridge::getLabelSuffix() const BOOL copy = item->getPermissions().allowCopyBy(gAgent.getID()); if (!copy) { + suffix += " "; suffix += NO_COPY; } BOOL mod = item->getPermissions().allowModifyBy(gAgent.getID()); if (!mod) { - suffix += NO_MOD; + suffix += suffix.empty() ? " " : ","; + suffix += NO_MOD; } BOOL xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); if (!xfer) { + suffix += suffix.empty() ? " " : ","; suffix += NO_XFER; } } @@ -2161,6 +2223,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(); @@ -2244,13 +2321,32 @@ void LLFolderBridge::buildDisplayName() const std::string LLFolderBridge::getLabelSuffix() const { static LLCachedControl<F32> folder_loading_message_delay(gSavedSettings, "FolderLoadingMessageWaitTime", 0.5f); + static LLCachedControl<bool> 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; @@ -2274,6 +2370,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) @@ -2484,7 +2590,8 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, BOOL drop, std::string& tooltip_msg, BOOL is_link, - BOOL user_confirm) + BOOL user_confirm, + LLPointer<LLInventoryCallback> cb) { LLInventoryModel* model = getInventoryModel(); @@ -2501,8 +2608,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); @@ -2520,10 +2627,10 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, if (is_agent_inventory) { - const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH, false); - 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 &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); const BOOL move_is_into_trash = (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); @@ -2789,7 +2896,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 && @@ -2799,14 +2906,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); } @@ -2818,6 +2927,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) { @@ -2836,14 +2946,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()); } } } @@ -2857,7 +2973,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<void(S32, void*, const LLMoveInv*)> 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) @@ -2928,7 +3059,7 @@ void warn_move_inventory(LLViewerObject* object, boost::shared_ptr<LLMoveInv> mo BOOL move_inv_category_world_to_agent(const LLUUID& object_id, const LLUUID& category_id, BOOL drop, - void (*callback)(S32, void*), + std::function<void(S32, void*, const LLMoveInv*)> callback, void* user_data, LLInventoryFilter* filter) { @@ -3231,6 +3362,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(); @@ -3300,18 +3437,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))) - { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantListingFailed", subs); - } - else + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [this](bool result) { - 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; } @@ -3319,18 +3464,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)) - { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantFolderActivationFailed", subs); - } - else + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) { - 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; } @@ -3353,29 +3507,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) + { + 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 { - LLNotificationsUtil::add("MerchantForceValidateListing"); + 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) @@ -3419,7 +3588,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))); } } @@ -3669,7 +3838,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; @@ -3730,11 +3899,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); @@ -3745,6 +3914,13 @@ void LLFolderBridge::perform_pasteFromClipboard() std::vector<LLUUID> objects; LLClipboard::instance().pasteFromClipboard(objects); + + LLPointer<LLInventoryCallback> 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) @@ -3820,7 +3996,7 @@ void LLFolderBridge::perform_pasteFromClipboard() { if (!move_is_into_my_outfits && 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()) { @@ -3828,7 +4004,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 { @@ -3844,7 +4020,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 { @@ -3863,11 +4039,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); } } } @@ -3895,6 +4072,7 @@ void LLFolderBridge::perform_pasteFromClipboard() //changeCategoryParent() implicity calls dirtyFilter changeCategoryParent(model, vicat, parent_id, FALSE); } + if (cb) cb->fire(item_id); } } else @@ -3916,6 +4094,7 @@ void LLFolderBridge::perform_pasteFromClipboard() //changeItemParent() implicity calls dirtyFilter changeItemParent(model, viitem, parent_id, FALSE); } + if (cb) cb->fire(item_id); } } } @@ -3936,6 +4115,7 @@ void LLFolderBridge::perform_pasteFromClipboard() { copy_inventory_category(model, vicat, parent_id); } + if (cb) cb->fire(item_id); } } else @@ -3951,11 +4131,13 @@ void LLFolderBridge::perform_pasteFromClipboard() // Stop pasting into the marketplace as soon as we get an error break; } + if (cb) cb->fire(item_id); } else if (item->getIsLinkType()) { - link_inventory_object(parent_id, item_id, - LLPointer<LLInventoryCallback>(NULL)); + link_inventory_object(parent_id, + item_id, + cb); } else { @@ -3965,7 +4147,7 @@ void LLFolderBridge::perform_pasteFromClipboard() item->getUUID(), parent_id, std::string(), - LLPointer<LLInventoryCallback>(NULL)); + cb); } } } @@ -3982,9 +4164,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); @@ -4001,6 +4183,14 @@ void LLFolderBridge::pasteLinkFromClipboard() std::vector<LLUUID> objects; LLClipboard::instance().pasteFromClipboard(objects); + + LLPointer<LLInventoryCallback> cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + if (panel->getRootFolder()->isSingleFolderMode()) + { + cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } + for (std::vector<LLUUID>::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) @@ -4011,12 +4201,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<LLInventoryObject> obj = model->getObject(object_id)) { - link_inventory_object(parent_id, obj, LLPointer<LLInventoryCallback>(NULL)); + link_inventory_object(parent_id, obj, cb); } } // Change mode to paste for next paste @@ -4054,8 +4244,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); if (outfits_id == mUUID) { @@ -4077,12 +4267,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) @@ -4105,11 +4289,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) @@ -4119,14 +4298,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")); @@ -4145,6 +4324,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()) { @@ -4169,19 +4350,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items } if (!isMarketplaceListingsFolder()) { - 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); @@ -4192,6 +4361,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 @@ -4206,6 +4376,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 @@ -4312,9 +4483,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")); @@ -4327,7 +4501,7 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& const bool is_agent_inventory = isAgentInventory(); // 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)) @@ -4347,6 +4521,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) { @@ -4372,26 +4554,29 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& if (type != LLFolderType::FT_OUTFIT) { items.push_back(std::string("Add To Outfit")); + 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)) + { + 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)) - { - disabled_items.push_back(std::string("Replace Outfit")); - } - 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")); @@ -4424,6 +4609,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(); @@ -4440,6 +4639,18 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop, { LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + static LLPointer<LLInventoryCallback> 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) @@ -4457,7 +4668,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. @@ -4468,12 +4679,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: @@ -4483,7 +4694,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: @@ -4493,6 +4704,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; } @@ -4793,138 +5009,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) -{ - LLInventoryType::EType inv_type = inv_item->getInventoryType(); - if ((inv_type != LLInventoryType::IT_WEARABLE) && - (inv_type != LLInventoryType::IT_GESTURE) && - (inv_type != LLInventoryType::IT_ATTACHMENT) && - (inv_type != LLInventoryType::IT_OBJECT) && - (inv_type != LLInventoryType::IT_SNAPSHOT) && - (inv_type != LLInventoryType::IT_TEXTURE)) - { - return FALSE; - } - - U32 flags = inv_item->getFlags(); - if(flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) - { - return FALSE; - } - - if((inv_type == LLInventoryType::IT_TEXTURE) || (inv_type == LLInventoryType::IT_SNAPSHOT)) - { - return !move_is_into_current_outfit; - } - - if (move_is_into_current_outfit && get_is_item_worn(inv_item->getUUID())) - { - return FALSE; - } - - return TRUE; -} - -// Returns true if 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) +void drop_to_favorites_cb(const LLUUID& id, LLPointer<LLInventoryCallback> cb1, LLPointer<LLInventoryCallback> cb2) { - // 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(); + cb1->fire(id); + cb2->fire(id); } -void LLFolderBridge::dropToFavorites(LLInventoryItem* inv_item) +void LLFolderBridge::dropToFavorites(LLInventoryItem* inv_item, LLPointer<LLInventoryCallback> 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<AddFavoriteLandmarkCallback> cb = new AddFavoriteLandmarkCallback(); + LLPointer<AddFavoriteLandmarkCallback> 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<LLFolderViewModelItemInventory*>(drag_over_item->getViewModelItem()) : NULL; if (view_model) { - cb.get()->setTargetLandmarkId(view_model->getUUID()); + cb_fav.get()->setTargetLandmarkId(view_model->getUUID()); } + LLPointer <LLInventoryCallback> 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<LLInventoryCallback> 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<LLFloaterOutfitPhotoPreview>("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; } @@ -4941,21 +5075,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<LLInventoryCallback> 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<LLInventoryCallback> cb) { LLInventoryModel::cat_array_t* categories; LLInventoryModel::item_array_t* items; @@ -4986,7 +5121,6 @@ void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID ca if (!link_array.empty()) { - LLPointer<LLInventoryCallback> cb = NULL; link_inventory_array(cat_dest_id, link_array, cb); } } @@ -5019,7 +5153,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<LLInventoryCallback> cb) { LLInventoryModel* model = getInventoryModel(); @@ -5033,11 +5168,11 @@ 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 &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 ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); const LLUUID from_folder_uuid = inv_item->getParentUUID(); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); @@ -5057,7 +5192,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()); @@ -5199,26 +5334,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()); } @@ -5228,6 +5364,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) @@ -5236,11 +5373,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); + } + }); } } @@ -5308,11 +5449,16 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, if (accept && drop) { + LLUUID item_id = inv_item->getUUID(); boost::shared_ptr<LLMoveInv> 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) { @@ -5404,13 +5550,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 { @@ -5420,7 +5566,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, inv_item->getUUID(), mUUID, std::string(), - LLPointer<LLInventoryCallback>(NULL)); + cb); } } } @@ -5975,7 +6121,7 @@ std::string LLCallingCardBridge::getLabelSuffix() const LLViewerInventoryItem* item = getItem(); if( item && LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()) ) { - return LLItemBridge::getLabelSuffix() + " (online)"; + return LLItemBridge::getLabelSuffix() + " online"; } else { @@ -7558,16 +7704,26 @@ 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 + { + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. + } +} + class LLLSLTextBridgeAction: public LLInvFVBridgeAction { friend class LLInvFVBridgeAction; @@ -7626,7 +7782,17 @@ void LLWearableBridgeAction::wearOnAvatar() LLViewerInventoryItem* item = getItem(); if(item) { - LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); + if (get_is_item_worn(mUUID)) + { + if(item->getType() != LLAssetType::AT_BODYPART) + { + LLAppearanceMgr::instance().removeItemFromAvatar(item->getUUID()); + } + } + else + { + LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); + } } } diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index bdffecf1c6..decaee7db3 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<std::string> menuentry_vec_t; - +typedef std::pair<LLUUID, LLUUID> two_uuids_t; +typedef std::list<two_uuids_t> 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() {} @@ -107,6 +109,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; } @@ -114,6 +117,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; @@ -251,6 +255,7 @@ public: virtual LLUIImagePtr getIconOverlay() const; LLViewerInventoryItem* getItem() const; + virtual const LLUUID& getThumbnailUUID() const; protected: BOOL confirmRemoveItem(const LLSD& notification, const LLSD& response); @@ -275,8 +280,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<LLInventoryCallback> cb = NULL); + BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop, std::string& tooltip_msg, BOOL is_link = FALSE, BOOL user_confirm = TRUE, LLPointer<LLInventoryCallback> 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); @@ -296,6 +301,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;} @@ -335,6 +341,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 @@ -360,9 +367,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<LLInventoryCallback> cb = NULL); + void dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit, LLPointer<LLInventoryCallback> cb = NULL); + void dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer<LLInventoryCallback> cb = NULL); //-------------------------------------------------------------------- // Messy hacks for handling folder options @@ -372,7 +379,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<LLInventoryCallback> cb); void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response); void perform_pasteFromClipboard(); void gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level); @@ -741,7 +748,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<void(S32, void*, const LLMoveInv *)> callback = NULL, void* user_data = NULL, LLInventoryFilter* filter = NULL); @@ -767,4 +774,16 @@ public: bool canWearSelected(const uuid_vec_t& item_ids) const; }; +struct LLMoveInv +{ + LLUUID mObjectID; + LLUUID mCategoryID; + two_uuids_list_t mMoveList; + std::function<void(S32, void*, const LLMoveInv*)> mCallback; + void* mUserData; +}; + +void warn_move_inventory(LLViewerObject* object, boost::shared_ptr<LLMoveInv> move_inv); +bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, boost::shared_ptr<LLMoveInv>); + #endif // LL_LLINVENTORYBRIDGE_H diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index e3a6b2dc85..5cf6c3fb7d 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -63,6 +63,7 @@ LLInventoryFilter::FilterOps::FilterOps(const Params& p) mFilterTypes(p.types), mFilterUUID(p.uuid), mFilterLinks(p.links), + mFilterThumbnails(p.thumbnails), mSearchVisibility(p.search_visibility) { } @@ -81,7 +82,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(); @@ -157,6 +159,8 @@ bool LLInventoryFilter::check(const LLFolderViewModelItem* item) passed = passed && checkAgainstCreator(listener); passed = passed && checkAgainstSearchVisibility(listener); + passed = passed && checkAgainstFilterThumbnails(listener->getUUID()); + return passed; } @@ -194,17 +198,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 | @@ -565,6 +575,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; @@ -595,6 +618,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; @@ -733,6 +759,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; @@ -791,6 +843,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; @@ -1503,6 +1573,11 @@ U64 LLInventoryFilter::getSearchVisibilityTypes() const return mFilterOps.mSearchVisibility; } +U64 LLInventoryFilter::getFilterThumbnails() const +{ + return mFilterOps.mFilterThumbnails; +} + bool LLInventoryFilter::hasFilterString() const { return mFilterSubString.size() > 0; @@ -1580,9 +1655,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); } @@ -1605,7 +1680,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 384de3e889..ada1d0f4b4 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 @@ -104,7 +111,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 @@ -139,12 +147,14 @@ public: Optional<EFolderShow> show_folder_state; Optional<PermissionMask> permissions; Optional<EFilterCreatorType> creator_type; + Optional<EFilterThumbnail> thumbnails; Params() : types("filter_types", FILTERTYPE_OBJECT), 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), @@ -165,6 +175,7 @@ public: U64 mFilterObjectTypes, // For _OBJECT mFilterWearableTypes, mFilterSettingsTypes, // for _SETTINGS + mFilterThumbnails, mFilterLinks, mFilterCategoryTypes; // For _CATEGORY LLUUID mFilterUUID; // for UUID @@ -207,6 +218,7 @@ public: U64 getFilterWearableTypes() const; U64 getFilterSettingsTypes() const; U64 getSearchVisibilityTypes() const; + U64 getFilterThumbnails() const; bool isFilterObjectTypesWith(LLInventoryType::EType t) const; void setFilterObjectTypes(U64 types); @@ -221,6 +233,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; } @@ -228,6 +241,7 @@ public: void toggleSearchVisibilityLinks(); void toggleSearchVisibilityTrash(); + void toggleSearchVisibilityOutfits(); void toggleSearchVisibilityLibrary(); void setSearchVisibilityTypes(U32 types); void setSearchVisibilityTypes(const Params& params); @@ -237,6 +251,8 @@ public: const std::string& getFilterSubStringOrig() const { return mFilterSubStringOrig; } bool hasFilterString() const; + void setSingleFolderMode(bool is_single_folder) { mSingleFolderMode = is_single_folder; } + void setFilterPermissions(PermissionMask perms); PermissionMask getFilterPermissions() const; @@ -277,7 +293,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 @@ -321,6 +337,8 @@ public: LLInventoryFilter& operator =(const LLInventoryFilter& other); + bool checkAgainstFilterThumbnails(const LLUUID& object_id) const; + private: bool areDateLimitsSet(); bool checkAgainstFilterType(const class LLFolderViewModelItemInventory* listener) const; @@ -359,6 +377,8 @@ private: std::vector<std::string> mFilterTokens; std::string mExactToken; + + bool mSingleFolderMode; }; #endif diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 145814ab41..4aeacae6ed 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" @@ -93,6 +96,7 @@ BOOL LLInventoryState::sWearNewClothing = FALSE; LLUUID LLInventoryState::sWearNewClothingTransactionID; std::list<LLUUID> LLInventoryAction::sMarketplaceFolders; +bool LLInventoryAction::sDeleteConfirmationDisplayed = false; // Helper function : callback to update a folder after inventory action happened in the background void update_folder_cb(const LLUUID& dest_folder) @@ -400,7 +404,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); @@ -426,14 +430,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) @@ -558,11 +584,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. if (LLAppearanceMgr::instance().isLinkedInCOF(id)) { @@ -797,18 +824,37 @@ BOOL get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id) void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id) { - 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); } void show_item_profile(const LLUUID& item_uuid) { LLUUID linked_uuid = gInventory.getLinkedItemID(item_uuid); - LLFloaterSidePanelContainer::showPanel("inventory", LLSD().with("id", linked_uuid)); + LLFloaterReg::showInstance("item_properties", LLSD().with("id", linked_uuid)); } void show_item_original(const LLUUID& item_uuid) { - LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); + static LLUICachedControl<bool> 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; @@ -820,6 +866,10 @@ void show_item_original(const LLUUID& item_uuid) LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); if (main_inventory) { + if(main_inventory->isSingleFolderMode()) + { + main_inventory->toggleViewMode(); + } main_inventory->resetAllItemsFilters(); } reset_inventory_filter(); @@ -828,7 +878,6 @@ void show_item_original(const LLUUID& item_uuid) { LLFloaterReg::toggleInstanceOrBringToFront("inventory"); } - sidepanel_inventory->showInventoryPanel(); const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); if (gInventory.isObjectDescendentOf(gInventory.getLinkedItemID(item_uuid), inbox_id)) @@ -848,6 +897,7 @@ void show_item_original(const LLUUID& item_uuid) } } } + } } @@ -869,22 +919,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 // @@ -899,7 +933,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; @@ -1371,6 +1405,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 @@ -1410,55 +1445,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<void(const LLUUID&)> 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<LLInventoryCallback> 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<void(const LLUUID&)> 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<LLInventoryCallback> 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 @@ -1507,7 +1606,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 @@ -1532,32 +1631,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<void(S32 pending_callbacks, bool result)> 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(); @@ -1589,10 +1679,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); } } } @@ -1602,26 +1692,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); } } } @@ -1652,10 +1762,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; } @@ -1686,35 +1796,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); } } } @@ -1722,10 +1832,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 @@ -1747,11 +1857,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) @@ -1762,30 +1873,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<LLUUID> 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<LLUUID>::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++; } } @@ -1799,11 +1951,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 @@ -1814,20 +1966,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 @@ -1842,21 +1994,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); } } } @@ -1865,17 +2017,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; } } @@ -1888,15 +2041,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) @@ -1996,9 +2149,346 @@ 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) +{ + LLInventoryType::EType inv_type = inv_item->getInventoryType(); + if ((inv_type != LLInventoryType::IT_WEARABLE) && + (inv_type != LLInventoryType::IT_GESTURE) && + (inv_type != LLInventoryType::IT_ATTACHMENT) && + (inv_type != LLInventoryType::IT_OBJECT) && + (inv_type != LLInventoryType::IT_SNAPSHOT) && + (inv_type != LLInventoryType::IT_TEXTURE)) + { + return false; + } + + U32 flags = inv_item->getFlags(); + if(flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) + { + return false; + } + + if((inv_type == LLInventoryType::IT_TEXTURE) || (inv_type == LLInventoryType::IT_SNAPSHOT)) + { + return !move_is_into_current_outfit; + } + + if (move_is_into_current_outfit && get_is_item_worn(inv_item->getUUID())) + { + return false; + } + + return true; +} + +// Returns TRUE if item is a landmark or a link to a landmark +// and can be moved to Favorites or Landmarks folder. +bool can_move_to_landmarks(LLInventoryItem* inv_item) +{ + // Need to get the linked item to know its type because LLInventoryItem::getType() + // returns actual type AT_LINK for links, not the asset type of a linked item. + if (LLAssetType::AT_LINK == inv_item->getType()) + { + LLInventoryItem* linked_item = gInventory.getItem(inv_item->getLinkedUUID()); + if (linked_item) + { + return LLAssetType::AT_LANDMARK == linked_item->getType(); + } + } + + return LLAssetType::AT_LANDMARK == inv_item->getType(); +} + +// Returns true if folder's content can be moved to Current Outfit or any outfit folder. +bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit) +{ + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + model->getDirectDescendentsOf(inv_cat->getUUID(), cats, items); + + if (items->size() > wear_limit) + { + return false; + } + + if (items->size() == 0) + { + // Nothing to move(create) + return false; + } + + if (cats->size() > 0) + { + // We do not allow subfolders in outfits of "My Outfits" yet + return false; + } + + LLInventoryModel::item_array_t::iterator iter = items->begin(); + LLInventoryModel::item_array_t::iterator end = items->end(); + + while (iter != end) + { + LLViewerInventoryItem *item = *iter; + if (!can_move_to_outfit(item, false)) + { + return false; + } + iter++; + } + + return true; +} + +std::string get_localized_folder_name(LLUUID cat_uuid) +{ + std::string localized_root_name; + const LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); + if (cat) + { + LLFolderType::EType preferred_type = cat->getPreferredType(); + + // Translation of Accessories folder in Library inventory folder + bool accessories = false; + if(cat->getName() == "Accessories") + { + const LLUUID& parent_folder_id = cat->getParentUUID(); + accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); + } + + //"Accessories" inventory category has folder type FT_NONE. So, this folder + //can not be detected as protected with LLFolderType::lookupIsProtectedType + localized_root_name.assign(cat->getName()); + if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) + { + LLTrans::findString(localized_root_name, std::string("InvFolder ") + cat->getName(), LLSD()); + } + } + + return localized_root_name; +} + +void new_folder_window(const LLUUID& folder_id) +{ + LLPanelMainInventory::newFolderWindow(folder_id); +} + +void ungroup_folder_items(const LLUUID& folder_id) +{ + LLInventoryCategory* inv_cat = gInventory.getCategory(folder_id); + if (!inv_cat || LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + return; + } + 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(); +} + +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; + if (LLAvatarNameCache::get(item->getCreatorUUID(), &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())) + { + 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 ///---------------------------------------------------------------------------- @@ -2182,6 +2672,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) { @@ -2247,6 +2750,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) @@ -2514,8 +3022,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root if ("delete" == action) { - 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<LLFolderViewItem*>::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) @@ -2538,10 +3045,10 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root } else { - 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; @@ -2593,7 +3100,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root LLMultiPreview* multi_previewp = NULL; - LLMultiProperties* multi_propertiesp = NULL; + LLMultiItemProperties* multi_itempropertiesp = nullptr; if (("task_open" == action || "open" == action) && selected_items.size() > 1) { @@ -2627,10 +3134,9 @@ 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); + multi_itempropertiesp = new LLMultiItemProperties("item_properties"); + gFloaterView->addChild(multi_itempropertiesp); + LLFloater::setFloaterHost(multi_itempropertiesp); } std::set<LLUUID> selected_uuid_set = LLAvatarActions::getInventorySelectedUUIDs(); @@ -2640,7 +3146,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), @@ -2755,36 +3261,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; - } - 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(); + ungroup_folder_items(*ids.begin()); } } else @@ -2798,6 +3275,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 @@ -2808,9 +3293,9 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { multi_previewp->openFloater(LLSD()); } - else if (multi_propertiesp) + else if (multi_itempropertiesp) { - multi_propertiesp->openFloater(LLSD()); + multi_itempropertiesp->openFloater(LLSD()); } } @@ -2898,7 +3383,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 8c8bd789c2..925217dda3 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -75,6 +75,7 @@ void update_all_marketplace_count(); 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); @@ -91,13 +92,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<void(std::string& validation_message, S32 depth, LLError::ELevel log_level)> 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); @@ -108,10 +107,65 @@ void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected bool is_only_cats_selected(const uuid_vec_t& selected_uuids); bool is_only_items_selected(const uuid_vec_t& selected_uuids); +bool can_move_to_outfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit); +bool can_move_to_landmarks(LLInventoryItem* inv_item); +bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit); +std::string get_localized_folder_name(LLUUID cat_uuid); +void new_folder_window(const LLUUID& folder_id); +void ungroup_folder_items(const LLUUID& folder_id); +std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id); +std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id); +std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id); +bool can_share_item(const LLUUID& item_id); + /** Miscellaneous global functions ** ** *******************************************************************************/ +class LLMarketplaceValidator: public LLSingleton<LLMarketplaceValidator> +{ + LLSINGLETON(LLMarketplaceValidator); + ~LLMarketplaceValidator(); + LOG_CLASS(LLMarketplaceValidator); +public: + + typedef boost::function<void(std::string& validation_message, S32 depth, LLError::ELevel log_level)> validation_msg_callback_t; + typedef boost::function<void(bool result)> 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<ValidationRequest> mValidationQueue; +}; + /******************************************************************************** ** ** ** INVENTORY COLLECTOR FUNCTIONS @@ -328,6 +382,20 @@ public: }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindBrokenLinks +// +// Collects broken links +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindBrokenLinks : public LLInventoryCollectFunctor +{ +public: + LLFindBrokenLinks() {} + virtual ~LLFindBrokenLinks() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLFindByMask //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class LLFindByMask : public LLInventoryCollectFunctor @@ -425,6 +493,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 { @@ -484,7 +561,7 @@ struct LLInventoryAction static void saveMultipleTextures(const std::vector<std::string>& filenames, std::set<LLFolderViewItem*> selected_items, LLInventoryModel* model); - static const int sConfirmOnDeleteItemsNumber; + 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..4838ba7a47 --- /dev/null +++ b/indra/newview/llinventorygallery.cpp @@ -0,0 +1,3824 @@ +/** + * @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<LLInventoryGallery> 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<LLInventoryGallery>(); +} + +BOOL LLInventoryGallery::postBuild() +{ + mScrollPanel = getChild<LLScrollContainer>("gallery_scroll_panel"); + LLPanel::Params params = LLPanel::getDefaultParams(); + mGalleryPanel = LLUICtrlFactory::create<LLPanel>(params); + mMessageTextBox = getChild<LLTextBox>("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<LLInventoryGalleryItem*> buf_items = mItems; + for (std::vector<LLInventoryGalleryItem*>::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it) + { + removeFromGalleryLast(*it, false); + } + for (std::vector<LLInventoryGalleryItem*>::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<LLInventoryGalleryItem*>::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<LLInventoryGalleryItem*> 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<LLInventoryGalleryItem>(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<LLGalleryPanel>(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<LLPanel>(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<LLPanel>(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<LLUUID> 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<LLUUID>& objects, + bool is_cut_mode, + const LLUUID& marketplacelistings_id) +{ + LLHandle<LLPanel> handle = getHandle(); + std::function <void(const LLUUID)> on_copy_callback = NULL; + LLPointer<LLInventoryCallback> 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<LLUUID>::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<LLUUID> 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() +{ + if (!LLInventoryAction::sDeleteConfirmationDisplayed) // ask for the confirmation at least once per session + { + LLNotifications::instance().setIgnored("DeleteItems", false); + LLInventoryAction::sDeleteConfirmationDisplayed = true; + } + + LLSD args; + args["QUESTION"] = LLTrans::getString("DeleteItem"); + 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<LLUUID> 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<LLUUID>& objects, + const LLUUID& current_outfit_id, + const LLUUID& marketplacelistings_id, + const LLUUID& my_outifts_id) +{ + const BOOL move_is_into_current_outfit = (dest == current_outfit_id); + const BOOL move_is_into_my_outfits = (dest == my_outifts_id) || gInventory.isObjectDescendentOf(dest, my_outifts_id); + const BOOL move_is_into_marketplacelistings = gInventory.isObjectDescendentOf(dest, marketplacelistings_id); + + if (move_is_into_marketplacelistings || move_is_into_current_outfit || move_is_into_my_outfits) + { + return; + } + + LLPointer<LLInventoryCallback> cb = NULL; + if (dest == mFolderID) + { + LLHandle<LLPanel> handle = getHandle(); + std::function <void(const LLUUID)> on_link_callback = [handle](const LLUUID& inv_item) + { + LLInventoryGallery* panel = (LLInventoryGallery*)handle.get(); + if (panel) + { + // Scroll to pasted item and highlight it + // Should it only highlight the last one? + panel->addItemSelection(inv_item, true); + } + }; + cb = new LLBoostFuncInventoryCallback(on_link_callback); + } + + for (std::vector<LLUUID>::const_iterator iter = objects.begin(); + iter != objects.end(); + ++iter) + { + const LLUUID& object_id = (*iter); + if (LLConstPointer<LLInventoryObject> link_obj = gInventory.getObject(object_id)) + { + link_inventory_object(dest, link_obj, cb); + } + } +} + +void LLInventoryGallery::claimEditHandler() +{ + gEditMenuHandler = this; +} + +void LLInventoryGallery::resetEditHandler() +{ + if (gEditMenuHandler == this) + { + gEditMenuHandler = NULL; + } +} + +bool LLInventoryGallery::isItemCopyable(const LLUUID & item_id) +{ + const LLInventoryCategory* cat = gInventory.getCategory(item_id); + if (cat) + { + // Folders are copyable if items in them are, recursively, copyable. + // Get the content of the folder + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(item_id, cat_array, item_array); + + // Check the items + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + if (!isItemCopyable(item->getUUID())) + { + return false; + } + } + + // Check the folders + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory* category = *iter; + if (!isItemCopyable(category->getUUID())) + { + return false; + } + } + + return true; + } + + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + // Can't copy worn objects. + // Worn objects are tied to their inworld conterparts + // Copy of modified worn object will return object with obsolete asset and inventory + if (get_is_item_worn(item_id)) + { + return false; + } + + static LLCachedControl<bool> inventory_linking(gSavedSettings, "InventoryLinking", true); + return (item->getIsLinkType() && inventory_linking) + || item->getPermissions().allowCopyBy(gAgent.getID()); + } + + return false; +} + +void LLInventoryGallery::updateMessageVisibility() +{ + + mMessageTextBox->setVisible(mItems.empty()); + if(mItems.empty()) + { + mMessageTextBox->setText(hasDescendents(mFolderID) ? LLTrans::getString("InventorySingleFolderEmpty") : LLTrans::getString("InventorySingleFolderNoMatches")); + } + + mScrollPanel->setVisible(!mItems.empty()); +} + +void LLInventoryGallery::refreshList(const LLUUID& category_id) +{ + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + + gInventory.getDirectDescendentsOf(category_id, cat_array, item_array); + uuid_vec_t vadded; + uuid_vec_t vremoved; + + // Create added and removed items vectors. + computeDifference(*cat_array, *item_array, vadded, vremoved); + + // Handle added tabs. + for (uuid_vec_t::const_iterator iter = vadded.begin(); + iter != vadded.end(); + ++iter) + { + const LLUUID cat_id = (*iter); + updateAddedItem(cat_id); + mNeedsArrange = true; + } + + // Handle removed tabs. + for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter) + { + const LLUUID cat_id = (*iter); + updateRemovedItem(cat_id); + } + + const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs(); + for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin(); + items_iter != changed_items.end(); + ++items_iter) + { + LLInventoryObject* obj = gInventory.getObject(*items_iter); + if(!obj) + { + return; + } + + updateChangedItemName(*items_iter, obj->getName()); + mNeedsArrange = true; + } + + if(mNeedsArrange || !mItemsToSelect.empty()) + { + // Don't scroll to target/arrange immediately + // since more updates might be pending + gIdleCallbacks.addFunction(onIdle, (void*)this); + } + updateMessageVisibility(); +} + +void LLInventoryGallery::computeDifference( + const LLInventoryModel::cat_array_t vcats, + const LLInventoryModel::item_array_t vitems, + uuid_vec_t& vadded, + uuid_vec_t& vremoved) +{ + uuid_vec_t vnew; + // Creating a vector of newly collected UUIDs. + for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin(); + iter != vcats.end(); + iter++) + { + vnew.push_back((*iter)->getUUID()); + } + for (LLInventoryModel::item_array_t::const_iterator iter = vitems.begin(); + iter != vitems.end(); + iter++) + { + vnew.push_back((*iter)->getUUID()); + } + + uuid_vec_t vcur; + getCurrentCategories(vcur); + std::copy(mItemBuildQuery.begin(), mItemBuildQuery.end(), std::back_inserter(vcur)); + + LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved); +} + +void LLInventoryGallery::onCOFChanged() +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + gInventory.collectDescendents( + LLAppearanceMgr::instance().getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + + uuid_vec_t vnew; + uuid_vec_t vadded; + uuid_vec_t vremoved; + + for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); + iter != item_array.end(); + ++iter) + { + vnew.push_back((*iter)->getLinkedUUID()); + } + + // We need to update only items that were added or removed from COF. + LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved); + + mCOFLinkedItems = vnew; + + for (uuid_vec_t::const_iterator iter = vadded.begin(); + iter != vadded.end(); + ++iter) + { + updateWornItem(*iter, true); + } + + for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter) + { + updateWornItem(*iter, false); + } +} + +void LLInventoryGallery::onGesturesChanged() +{ + uuid_vec_t vnew; + uuid_vec_t vadded; + uuid_vec_t vremoved; + + const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures(); + for (LLGestureMgr::item_map_t::const_iterator iter = active_gestures.begin(); + iter != active_gestures.end(); + ++iter) + { + vnew.push_back(iter->first); + } + + LLCommonUtils::computeDifference(vnew, mActiveGestures, vadded, vremoved); + + mActiveGestures = vnew; + + for (uuid_vec_t::const_iterator iter = vadded.begin(); + iter != vadded.end(); + ++iter) + { + updateWornItem(*iter, true); + } + + for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter) + { + updateWornItem(*iter, false); + } +} + +void LLInventoryGallery::deselectItem(const LLUUID& category_id) +{ + // Reset selection if the item is selected. + LLInventoryGalleryItem* item = mItemMap[category_id]; + if (item && item->isSelected()) + { + mItemMap[category_id]->setSelected(FALSE); + setFocus(true); + // Todo: support multiselect + // signalSelectionItemID(LLUUID::null); + } + + selection_deque::iterator found = std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), category_id); + if (found != mSelectedItemIDs.end()) + { + mSelectedItemIDs.erase(found); + } +} + +void LLInventoryGallery::clearSelection() +{ + for (const LLUUID& id: mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(FALSE); + } + } + if (!mSelectedItemIDs.empty()) + { + mSelectedItemIDs.clear(); + // BUG: wrong, item can be null + signalSelectionItemID(LLUUID::null); + } +} + +void LLInventoryGallery::signalSelectionItemID(const LLUUID& category_id) +{ + mSelectionChangeSignal(category_id); +} + +boost::signals2::connection LLInventoryGallery::setSelectionChangeCallback(selection_change_callback_t cb) +{ + return mSelectionChangeSignal.connect(cb); +} + +LLUUID LLInventoryGallery::getFirstSelectedItemID() +{ + if (mSelectedItemIDs.size() > 0) + { + return *mSelectedItemIDs.begin(); + } + return LLUUID::null; +} + +LLUUID LLInventoryGallery::getOutfitImageID(LLUUID outfit_id) +{ + LLUUID thumbnail_id; + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + // Not LLIsOfAssetType, because we allow links + LLIsTextureType f; + gInventory.getDirectDescendentsOf(outfit_id, cats, items, f); + + // Exactly one texture found => show the texture as thumbnail + if (1 == items.size()) + { + LLViewerInventoryItem* item = items.front(); + if (item && item->getIsLinkType()) + { + item = item->getLinkedItem(); + } + if (item) + { + thumbnail_id = item->getAssetUUID(); + } + } + } + return thumbnail_id; +} + +boost::signals2::connection LLInventoryGallery::setRootChangedCallback(callback_t cb) +{ + return mRootChangedSignal.connect(cb); +} + +void LLInventoryGallery::onForwardFolder() +{ + if(isForwardAvailable()) + { + mBackwardFolders.push_back(mFolderID); + mFolderID = mForwardFolders.back(); + mForwardFolders.pop_back(); + dirtyRootFolder(); + } +} + +void LLInventoryGallery::onBackwardFolder() +{ + if(isBackwardAvailable()) + { + mForwardFolders.push_back(mFolderID); + mFolderID = mBackwardFolders.back(); + mBackwardFolders.pop_back(); + dirtyRootFolder(); + } +} + +void LLInventoryGallery::clearNavigationHistory() +{ + mForwardFolders.clear(); + mBackwardFolders.clear(); +} + +bool LLInventoryGallery::isBackwardAvailable() +{ + return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back())); +} + +bool LLInventoryGallery::isForwardAvailable() +{ + return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back())); +} + +BOOL LLInventoryGallery::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void* cargo_data, + EAcceptance* accept, std::string& tooltip_msg) +{ + // have children handle it first + BOOL handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, + accept, tooltip_msg); + + // when drop is not handled by child, it should be handled by the root folder . + if (!handled || (*accept == ACCEPT_NO)) + { + handled = baseHandleDragAndDrop(mFolderID, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + + return handled; +} + +void LLInventoryGallery::startDrag() +{ + std::vector<EDragAndDropType> types; + uuid_vec_t ids; + LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_AGENT; + for (LLUUID& selected_id : mSelectedItemIDs) + { + const LLInventoryItem* item = gInventory.getItem(selected_id); + if (item) + { + if (item->getPermissions().getOwner() == ALEXANDRIA_LINDEN_ID) + { + src = LLToolDragAndDrop::SOURCE_LIBRARY; + } + + EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(item->getType()); + types.push_back(type); + ids.push_back(selected_id); + } + + const LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id); + if (cat && gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID()) + && !LLFolderType::lookupIsProtectedType((cat)->getPreferredType())) + { + if (cat->getOwnerID() == ALEXANDRIA_LINDEN_ID) + { + src = LLToolDragAndDrop::SOURCE_LIBRARY; + } + + EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(cat->getType()); + types.push_back(type); + ids.push_back(selected_id); + } + } + LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, LLToolDragAndDrop::SOURCE_AGENT); +} + +bool LLInventoryGallery::areViewsInitialized() +{ + return mGalleryCreated && mItemBuildQuery.empty(); +} + +bool LLInventoryGallery::hasDescendents(const LLUUID& cat_id) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + + return (cats->empty() && items->empty()); +} + +bool LLInventoryGallery::checkAgainstFilterType(const LLUUID& object_id) +{ + const LLInventoryObject *object = gInventory.getObject(object_id); + if(!object) return false; + + LLInventoryType::EType object_type = LLInventoryType::IT_CATEGORY; + LLInventoryItem* inv_item = gInventory.getItem(object_id); + if (inv_item) + { + object_type = inv_item->getInventoryType(); + } + const U32 filterTypes = mFilter->getFilterTypes(); + + if ((filterTypes & LLInventoryFilter::FILTERTYPE_OBJECT) && inv_item) + { + switch (object_type) + { + case LLInventoryType::IT_NONE: + // If it has no type, pass it, unless it's a link. + if (object && object->getIsLinkType()) + { + return false; + } + break; + case LLInventoryType::IT_UNKNOWN: + { + // Unknows are only shown when we show every type. + // Unknows are 255 and won't fit in 64 bits. + if (mFilter->getFilterObjectTypes() != 0xffffffffffffffffULL) + { + return false; + } + break; + } + default: + if ((1LL << object_type & mFilter->getFilterObjectTypes()) == U64(0)) + { + return false; + } + break; + } + } + + if (filterTypes & LLInventoryFilter::FILTERTYPE_DATE) + { + const U16 HOURS_TO_SECONDS = 3600; + time_t earliest = time_corrected() - mFilter->getHoursAgo() * HOURS_TO_SECONDS; + + if (mFilter->getMinDate() > time_min() && mFilter->getMinDate() < earliest) + { + earliest = mFilter->getMinDate(); + } + else if (!mFilter->getHoursAgo()) + { + earliest = 0; + } + + if (LLInventoryFilter::FILTERDATEDIRECTION_NEWER == mFilter->getDateSearchDirection() || mFilter->isSinceLogoff()) + { + if (object->getCreationDate() < earliest || + object->getCreationDate() > mFilter->getMaxDate()) + return false; + } + else + { + if (object->getCreationDate() > earliest || + object->getCreationDate() > mFilter->getMaxDate()) + return false; + } + } + return true; +} + +bool LLInventoryGallery::hasVisibleItems() +{ + return mItemsAddedCount > 0; +} + +void LLInventoryGallery::handleModifiedFilter() +{ + if(mFilter->isModified()) + { + reArrangeRows(); + } +} + +void LLInventoryGallery::setSortOrder(U32 order, bool update) +{ + bool dirty = (mSortOrder != order); + + mSortOrder = order; + if(update && dirty) + { + mNeedsArrange = true; + gIdleCallbacks.addFunction(onIdle, (void*)this); + } +} +//----------------------------- +// LLInventoryGalleryItem +//----------------------------- + +static LLDefaultChildRegistry::Register<LLInventoryGalleryItem> r("inventory_gallery_item"); + +LLInventoryGalleryItem::LLInventoryGalleryItem(const Params& p) + : LLPanel(p), + mSelected(false), + mDefaultImage(true), + mItemName(""), + mWornSuffix(""), + mPermSuffix(""), + mUUID(LLUUID()), + mIsFolder(true), + mIsLink(false), + mHidden(false), + mGallery(NULL), + mType(LLAssetType::AT_NONE), + mSortGroup(SG_ITEM), + mCutGeneration(0), + mSelectedForCut(false) +{ + buildFromFile("panel_inventory_gallery_item.xml"); +} + +LLInventoryGalleryItem::~LLInventoryGalleryItem() +{ +} + +BOOL LLInventoryGalleryItem::postBuild() +{ + mNameText = getChild<LLTextBox>("item_name"); + mTextBgPanel = getChild<LLPanel>("text_bg_panel"); + + return TRUE; +} + +void LLInventoryGalleryItem::setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link) +{ + mType = type; + mIsFolder = (mType == LLAssetType::AT_CATEGORY); + mIsLink = is_link; + + std::string icon_name = LLInventoryIcon::getIconName(mType, inventory_type, flags); + if(mIsFolder) + { + mSortGroup = SG_NORMAL_FOLDER; + LLUUID folder_id = mUUID; + if(mIsLink) + { + LLInventoryObject* obj = gInventory.getObject(mUUID); + if (obj) + { + folder_id = obj->getLinkedUUID(); + } + } + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat) + { + LLFolderType::EType preferred_type = cat->getPreferredType(); + icon_name = LLViewerFolderType::lookupIconName(preferred_type); + + if (preferred_type == LLFolderType::FT_TRASH) + { + mSortGroup = SG_TRASH_FOLDER; + } + else if(LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + { + mSortGroup = SG_SYSTEM_FOLDER; + } + } + } + else + { + const LLInventoryItem *item = gInventory.getItem(mUUID); + if(item && (LLAssetType::AT_CALLINGCARD != item->getType()) && !mIsLink) + { + std::string delim(" --"); + bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); + if (!copy) + { + mPermSuffix += delim; + mPermSuffix += LLTrans::getString("no_copy_lbl"); + } + bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); + if (!mod) + { + mPermSuffix += mPermSuffix.empty() ? delim : ","; + mPermSuffix += LLTrans::getString("no_modify_lbl"); + } + bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); + if (!xfer) + { + mPermSuffix += mPermSuffix.empty() ? delim : ","; + mPermSuffix += LLTrans::getString("no_transfer_lbl"); + } + } + } + + getChild<LLIconCtrl>("item_type")->setValue(icon_name); + getChild<LLIconCtrl>("link_overlay")->setVisible(is_link); +} + +void LLInventoryGalleryItem::setThumbnail(LLUUID id) +{ + mDefaultImage = id.isNull(); + if(mDefaultImage) + { + getChild<LLThumbnailCtrl>("preview_thumbnail")->clearTexture(); + } + else + { + getChild<LLThumbnailCtrl>("preview_thumbnail")->setValue(id); + } +} + +void LLInventoryGalleryItem::draw() +{ + if (isFadeItem()) + { + // Fade out to indicate it's being cut + LLViewDrawContext context(0.5f); + LLPanel::draw(); + } + else + { + LLPanel::draw(); + + // Draw border + LLUIColor border_color = LLUIColorTable::instance().getColor(mSelected ? "MenuItemHighlightBgColor" : "TextFgTentativeColor", LLColor4::white); + LLRect border = getChildView("preview_thumbnail")->getRect(); + border.mRight = border.mRight + 1; + border.mTop = border.mTop + 1; + gl_rect_2d(border, border_color.get(), FALSE); + } +} + +void LLInventoryGalleryItem::setItemName(std::string name) +{ + mItemName = name; + updateNameText(); +} + +void LLInventoryGalleryItem::setSelected(bool value) +{ + mSelected = value; + mTextBgPanel->setBackgroundVisible(value); + + if(mSelected) + { + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + if(item && !item->isFinished()) + { + LLInventoryModelBackgroundFetch::instance().start(mUUID, false); + } + } +} + +BOOL LLInventoryGalleryItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // call changeItemSelection directly, before setFocus + // to avoid autoscroll from LLInventoryGallery::onFocusReceived() + if (mask == MASK_CONTROL) + { + mGallery->addItemSelection(mUUID, false); + } + else if (mask == MASK_SHIFT) + { + mGallery->toggleSelectionRangeFromLast(mUUID); + } + else + { + mGallery->changeItemSelection(mUUID, false); + } + setFocus(TRUE); + mGallery->claimEditHandler(); + + gFocusMgr.setMouseCapture(this); + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); + return TRUE; +} + +BOOL LLInventoryGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (!isSelected()) + { + mGallery->changeItemSelection(mUUID, false); + } + else + { + // refresh last interacted + mGallery->addItemSelection(mUUID, false); + } + setFocus(TRUE); + mGallery->claimEditHandler(); + mGallery->showContextMenu(this, x, y, mUUID); + + LLUICtrl::handleRightMouseDown(x, y, mask); + return TRUE; +} + +BOOL LLInventoryGalleryItem::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if(hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + return TRUE; + } + return LLPanel::handleMouseUp(x, y, mask); +} + +BOOL LLInventoryGalleryItem::handleHover(S32 x, S32 y, MASK mask) +{ + if(hasMouseCapture()) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + + if(LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y) && mGallery) + { + mGallery->startDrag(); + return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask); + } + } + return LLUICtrl::handleHover(x,y,mask); +} + +BOOL LLInventoryGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (mIsFolder && mGallery) + { + // setRootFolder can destroy this item. + // Delay it until handleDoubleClick processing is complete + // or make gallery handle doubleclicks. + LLHandle<LLPanel> handle = mGallery->getHandle(); + LLUUID navigate_to = mUUID; + doOnIdleOneTime([handle, navigate_to]() + { + LLInventoryGallery* gallery = (LLInventoryGallery*)handle.get(); + if (gallery) + { + gallery->setRootFolder(navigate_to); + } + }); + } + else + { + LLInvFVBridgeAction::doAction(mUUID, &gInventory); + } + + return TRUE; +} + +BOOL LLInventoryGalleryItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (!mIsFolder) + { + return FALSE; + } + return mGallery->baseHandleDragAndDrop(mUUID, drop, cargo_type, cargo_data, accept, tooltip_msg); +} + +BOOL LLInventoryGalleryItem::handleKeyHere(KEY key, MASK mask) +{ + if (!mGallery) + { + return FALSE; + } + + BOOL handled = FALSE; + switch (key) + { + case KEY_LEFT: + mGallery->moveLeft(mask); + handled = true; + break; + + case KEY_RIGHT: + mGallery->moveRight(mask); + handled = true; + break; + + case KEY_UP: + mGallery->moveUp(mask); + handled = true; + break; + + case KEY_DOWN: + mGallery->moveDown(mask); + handled = true; + break; + + default: + break; + } + return handled; +} + +void LLInventoryGalleryItem::onFocusLost() +{ + // inventory no longer handles cut/copy/paste/delete + mGallery->resetEditHandler(); + + LLPanel::onFocusLost(); +} + +void LLInventoryGalleryItem::onFocusReceived() +{ + // inventory now handles cut/copy/paste/delete + mGallery->claimEditHandler(); + + LLPanel::onFocusReceived(); +} + +void LLInventoryGalleryItem::setWorn(bool value) +{ + mWorn = value; + + if(mWorn) + { + mWornSuffix = (mType == LLAssetType::AT_GESTURE) ? LLTrans::getString("active") : LLTrans::getString("worn"); + } + else + { + mWornSuffix = ""; + } + + updateNameText(); +} + +LLFontGL* LLInventoryGalleryItem::getTextFont() +{ + if(mWorn) + { + return LLFontGL::getFontSansSerifSmallBold(); + } + return mIsLink ? LLFontGL::getFontSansSerifSmallItalic() : LLFontGL::getFontSansSerifSmall(); +} + +void LLInventoryGalleryItem::updateNameText() +{ + mNameText->setFont(getTextFont()); + mNameText->setText(mItemName + mPermSuffix + mWornSuffix); + mNameText->setToolTip(mItemName + mPermSuffix + mWornSuffix); + getChild<LLThumbnailCtrl>("preview_thumbnail")->setToolTip(mItemName + mPermSuffix + mWornSuffix); +} + +bool LLInventoryGalleryItem::isFadeItem() +{ + LLClipboard& clipboard = LLClipboard::instance(); + if (mCutGeneration == clipboard.getGeneration()) + { + return mSelectedForCut; + } + + mCutGeneration = clipboard.getGeneration(); + mSelectedForCut = clipboard.isCutMode() && clipboard.isOnClipboard(mUUID); + return mSelectedForCut; +} + +//----------------------------- +// LLThumbnailsObserver +//----------------------------- + +void LLThumbnailsObserver::changed(U32 mask) +{ + std::vector<LLUUID> deleted_ids; + for (item_map_t::iterator iter = mItemMap.begin(); + iter != mItemMap.end(); + ++iter) + { + const LLUUID& obj_id = (*iter).first; + LLItemData& data = (*iter).second; + + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (!obj) + { + deleted_ids.push_back(obj_id); + continue; + } + + const LLUUID thumbnail_id = obj->getThumbnailUUID(); + if (data.mThumbnailID != thumbnail_id) + { + data.mThumbnailID = thumbnail_id; + data.mCallback(); + } + } + + // Remove deleted items from the list + for (std::vector<LLUUID>::iterator deleted_id = deleted_ids.begin(); deleted_id != deleted_ids.end(); ++deleted_id) + { + removeItem(*deleted_id); + } +} + +bool LLThumbnailsObserver::addItem(const LLUUID& obj_id, callback_t cb) +{ + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (obj) + { + mItemMap.insert(item_map_value_t(obj_id, LLItemData(obj_id, obj->getThumbnailUUID(), cb))); + return true; + } + return false; +} + +void LLThumbnailsObserver::removeItem(const LLUUID& obj_id) +{ + mItemMap.erase(obj_id); +} + +//----------------------------- +// Helper drag&drop functions +//----------------------------- + +BOOL LLInventoryGallery::baseHandleDragAndDrop(LLUUID dest_id, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + + if (drop && LLToolDragAndDrop::getInstance()->getCargoIndex() == 0) + { + clearSelection(); + } + + BOOL accepted = FALSE; + switch(cargo_type) + { + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_CALLINGCARD: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_MESH: + case DAD_SETTINGS: + accepted = dragItemIntoFolder(dest_id, inv_item, drop, tooltip_msg, true); + if (accepted && drop) + { + // Don't select immediately, wait for item to arrive + mItemsToSelect.push_back(inv_item->getUUID()); + } + break; + case DAD_LINK: + // DAD_LINK type might mean one of two asset types: AT_LINK or AT_LINK_FOLDER. + // If we have an item of AT_LINK_FOLDER type we should process the linked + // category being dragged or dropped into folder. + if (inv_item && LLAssetType::AT_LINK_FOLDER == inv_item->getActualType()) + { + LLInventoryCategory* linked_category = gInventory.getCategory(inv_item->getLinkedUUID()); + if (linked_category) + { + accepted = dragCategoryIntoFolder(dest_id, (LLInventoryCategory*)linked_category, drop, tooltip_msg, TRUE); + } + } + else + { + accepted = dragItemIntoFolder(dest_id, inv_item, drop, tooltip_msg, TRUE); + } + if (accepted && drop && inv_item) + { + mItemsToSelect.push_back(inv_item->getUUID()); + } + break; + case DAD_CATEGORY: + if (LLFriendCardsManager::instance().isAnyFriendCategory(dest_id)) + { + accepted = FALSE; + } + else + { + LLInventoryCategory* cat_ptr = (LLInventoryCategory*)cargo_data; + accepted = dragCategoryIntoFolder(dest_id, cat_ptr, drop, tooltip_msg, FALSE); + if (accepted && drop) + { + mItemsToSelect.push_back(cat_ptr->getUUID()); + } + } + break; + case DAD_ROOT_CATEGORY: + case DAD_NONE: + break; + default: + LL_WARNS() << "Unhandled cargo type for drag&drop " << cargo_type << LL_ENDL; + break; + } + if (accepted) + { + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + return accepted; +} + +// copy of LLFolderBridge::dragItemIntoFolder +BOOL dragItemIntoFolder(LLUUID folder_id, LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm) +{ + LLViewerInventoryCategory * cat = gInventory.getCategory(folder_id); + if (!cat) + { + return FALSE; + } + LLInventoryModel* model = &gInventory; + + if (!model || !inv_item) return FALSE; + + // cannot drag into library + if((gInventory.getRootFolderID() != folder_id) && !model->isObjectDescendentOf(folder_id, gInventory.getRootFolderID())) + { + return FALSE; + } + if (!isAgentAvatarValid()) return FALSE; + + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + const BOOL move_is_into_current_outfit = (folder_id == current_outfit_id); + const BOOL move_is_into_favorites = (folder_id == favorites_id); + const BOOL move_is_into_my_outfits = (folder_id == my_outifts_id) || model->isObjectDescendentOf(folder_id, my_outifts_id); + const BOOL move_is_into_outfit = move_is_into_my_outfits || (cat && cat->getPreferredType()==LLFolderType::FT_OUTFIT); + const BOOL move_is_into_landmarks = (folder_id == landmarks_id) || model->isObjectDescendentOf(folder_id, landmarks_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(folder_id, marketplacelistings_id); + const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(inv_item->getUUID(), marketplacelistings_id); + + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + BOOL accept = FALSE; + LLViewerObject* object = NULL; + if(LLToolDragAndDrop::SOURCE_AGENT == source) + { + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + + const BOOL move_is_into_trash = (folder_id == trash_id) || model->isObjectDescendentOf(folder_id, trash_id); + const BOOL move_is_outof_current_outfit = LLAppearanceMgr::instance().getIsInCOF(inv_item->getUUID()); + + //-------------------------------------------------------------------------------- + // Determine if item can be moved. + // + + BOOL is_movable = TRUE; + + switch (inv_item->getActualType()) + { + case LLAssetType::AT_CATEGORY: + is_movable = !LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)inv_item)->getPreferredType()); + break; + default: + break; + } + // Can't explicitly drag things out of the COF. + if (move_is_outof_current_outfit) + { + is_movable = FALSE; + } + if (move_is_into_trash) + { + is_movable &= inv_item->getIsLinkType() || !get_is_item_worn(inv_item->getUUID()); + } + if (is_movable) + { + // Don't allow creating duplicates in the Calling Card/Friends + // subfolders, see bug EXT-1599. Check is item direct descendent + // of target folder and forbid item's movement if it so. + // Note: isItemDirectDescendentOfCategory checks if + // passed category is in the Calling Card/Friends folder + is_movable &= !LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(inv_item, cat); + } + + // + //-------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------- + // Determine if item can be moved & dropped + // Note: if user_confirm is false, we already went through those accept logic test and can skip them + + accept = TRUE; + + if (user_confirm && !is_movable) + { + accept = FALSE; + } + else if (user_confirm && (folder_id == inv_item->getParentUUID()) && !move_is_into_favorites) + { + accept = FALSE; + } + else if (user_confirm && (move_is_into_current_outfit || move_is_into_outfit)) + { + accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); + } + else if (user_confirm && (move_is_into_favorites || move_is_into_landmarks)) + { + accept = can_move_to_landmarks(inv_item); + } + else if (user_confirm && move_is_into_marketplacelistings) + { + //disable dropping in or out of marketplace for now + return FALSE; + + /*const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, folder_id); + LLViewerInventoryCategory * dest_folder = cat; + accept = can_move_item_to_marketplace(master_folder, dest_folder, inv_item, tooltip_msg, LLToolDragAndDrop::instance().getCargoCount() - LLToolDragAndDrop::instance().getCargoIndex());*/ + } + + // Check that the folder can accept this item based on folder/item type compatibility (e.g. stock folder compatibility) + if (user_confirm && accept) + { + LLViewerInventoryCategory * dest_folder = cat; + accept = dest_folder->acceptItem(inv_item); + } + + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); + + if (accept && drop) + { + if (inv_item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash) + { + LLGestureMgr::instance().deactivateGesture(inv_item->getUUID()); + } + // If an item is being dragged between windows, unselect everything in the active window + // so that we don't follow the selection to its new location (which is very annoying). + // RN: a better solution would be to deselect automatically when an item is moved + // and then select any item that is dropped only in the panel that it is dropped in + if (active_panel) + { + active_panel->unSelectAll(); + } + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + //disable dropping in or out of marketplace for now + return FALSE; + } + + //-------------------------------------------------------------------------------- + // Destination folder logic + // + + // FAVORITES folder + // (copy the item) + else if (move_is_into_favorites) + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + folder_id, + std::string(), + LLPointer<LLInventoryCallback>(NULL)); + } + // CURRENT OUTFIT or OUTFIT folder + // (link the item) + else if (move_is_into_current_outfit || move_is_into_outfit) + { + if (move_is_into_current_outfit) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true); + } + else + { + LLPointer<LLInventoryCallback> cb = NULL; + link_inventory_object(folder_id, LLConstPointer<LLInventoryObject>(inv_item), cb); + } + } + // MARKETPLACE LISTINGS folder + // Move the item + else if (move_is_into_marketplacelistings) + { + //move_item_to_marketplacelistings(inv_item, mUUID); + return FALSE; + } + // NORMAL or TRASH folder + // (move the item, restamp if into trash) + else + { + // set up observer to select item once drag and drop from inbox is complete + if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX))) + { + set_dad_inbox_object(inv_item->getUUID()); + } + + gInventory.changeItemParent((LLViewerInventoryItem*)inv_item, folder_id, move_is_into_trash); + } + + if (move_is_from_marketplacelistings) + { + // If we move from an active (listed) listing, checks that it's still valid, if not, unlist + /*LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) + { + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + }); + }*/ + return FALSE; + } + + // + //-------------------------------------------------------------------------------- + } + } + else if (LLToolDragAndDrop::SOURCE_WORLD == source) + { + // Make sure the object exists. If we allowed dragging from + // anonymous objects, it would be possible to bypass + // permissions. + object = gObjectList.findObject(inv_item->getParentUUID()); + if (!object) + { + LL_INFOS() << "Object not found for drop." << LL_ENDL; + return FALSE; + } + + // coming from a task. Need to figure out if the person can + // move/copy this item. + LLPermissions perm(inv_item->getPermissions()); + bool is_move = false; + if ((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) + && perm.allowTransferTo(gAgent.getID()))) + // || gAgent.isGodlike()) + { + accept = TRUE; + } + else if(object->permYouOwner()) + { + // If the object cannot be copied, but the object the + // inventory is owned by the agent, then the item can be + // moved from the task to agent inventory. + is_move = true; + accept = TRUE; + } + + // Don't allow placing an original item into Current Outfit or an outfit folder + // because they must contain only links to wearable items. + if (move_is_into_current_outfit || move_is_into_outfit) + { + accept = FALSE; + } + // Don't allow to move a single item to Favorites or Landmarks + // if it is not a landmark or a link to a landmark. + else if ((move_is_into_favorites || move_is_into_landmarks) + && !can_move_to_landmarks(inv_item)) + { + accept = FALSE; + } + else if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + + if (accept && drop) + { + boost::shared_ptr<LLMoveInv> move_inv (new LLMoveInv()); + move_inv->mObjectID = inv_item->getParentUUID(); + std::pair<LLUUID, LLUUID> item_pair(folder_id, inv_item->getUUID()); + move_inv->mMoveList.push_back(item_pair); + move_inv->mCallback = NULL; + move_inv->mUserData = NULL; + if(is_move) + { + warn_move_inventory(object, move_inv); + } + else + { + // store dad inventory item to select added one later. See EXT-4347 + set_dad_inventory_item(inv_item, folder_id); + + LLNotification::Params params("MoveInventoryFromObject"); + params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv)); + LLNotifications::instance().forceResponse(params, 0); + } + } + } + else if(LLToolDragAndDrop::SOURCE_NOTECARD == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else if ((inv_item->getActualType() == LLAssetType::AT_SETTINGS) && !LLEnvironment::instance().isInventoryEnabled()) + { + tooltip_msg = LLTrans::getString("NoEnvironmentSettings"); + accept = FALSE; + } + else + { + // Don't allow placing an original item from a notecard to Current Outfit or an outfit folder + // because they must contain only links to wearable items. + accept = !(move_is_into_current_outfit || move_is_into_outfit); + } + + if (accept && drop) + { + copy_inventory_from_notecard(folder_id, // Drop to the chosen destination folder + LLToolDragAndDrop::getInstance()->getObjectID(), + LLToolDragAndDrop::getInstance()->getSourceID(), + inv_item); + } + } + else if(LLToolDragAndDrop::SOURCE_LIBRARY == source) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_item; + if(item && item->isFinished()) + { + accept = TRUE; + + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else if (move_is_into_current_outfit || move_is_into_outfit) + { + accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); + } + // Don't allow to move a single item to Favorites or Landmarks + // if it is not a landmark or a link to a landmark. + else if (move_is_into_favorites || move_is_into_landmarks) + { + accept = can_move_to_landmarks(inv_item); + } + + if (accept && drop) + { + // FAVORITES folder + // (copy the item) + if (move_is_into_favorites) + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + folder_id, + std::string(), + LLPointer<LLInventoryCallback>(NULL)); + } + // CURRENT OUTFIT or OUTFIT folder + // (link the item) + else if (move_is_into_current_outfit || move_is_into_outfit) + { + if (move_is_into_current_outfit) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true); + } + else + { + LLPointer<LLInventoryCallback> cb = NULL; + link_inventory_object(folder_id, LLConstPointer<LLInventoryObject>(inv_item), cb); + } + } + else + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + folder_id, + std::string(), + LLPointer<LLInventoryCallback>(NULL)); + } + } + } + } + else + { + LL_WARNS() << "unhandled drag source" << LL_ENDL; + } + return accept; +} + +// copy of LLFolderBridge::dragCategoryIntoFolder +BOOL dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat, + BOOL drop, std::string& tooltip_msg, BOOL is_link) +{ + BOOL user_confirm = TRUE; + LLInventoryModel* model = &gInventory; + LLViewerInventoryCategory * dest_cat = gInventory.getCategory(dest_id); + if (!dest_cat) + { + return FALSE; + } + + if (!inv_cat) return FALSE; // shouldn't happen, but in case item is incorrectly parented in which case inv_cat will be NULL + + if (!isAgentAvatarValid()) return FALSE; + // cannot drag into library + if((gInventory.getRootFolderID() != dest_id) && !model->isObjectDescendentOf(dest_id, gInventory.getRootFolderID())) + { + return FALSE; + } + + const LLUUID &cat_id = inv_cat->getUUID(); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + //const LLUUID from_folder_uuid = inv_cat->getParentUUID(); + + const BOOL move_is_into_current_outfit = (dest_id == current_outfit_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(dest_id, marketplacelistings_id); + const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(cat_id, marketplacelistings_id); + + // check to make sure source is agent inventory, and is represented there. + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + const BOOL is_agent_inventory = (model->getCategory(cat_id) != NULL) + && (LLToolDragAndDrop::SOURCE_AGENT == source); + + BOOL accept = FALSE; + + if (is_agent_inventory) + { + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + + const BOOL move_is_into_trash = (dest_id == trash_id) || model->isObjectDescendentOf(dest_id, trash_id); + const BOOL move_is_into_my_outfits = (dest_id == my_outifts_id) || model->isObjectDescendentOf(dest_id, my_outifts_id); + const BOOL move_is_into_outfit = move_is_into_my_outfits || (dest_cat && dest_cat->getPreferredType()==LLFolderType::FT_OUTFIT); + const BOOL move_is_into_current_outfit = (dest_cat && dest_cat->getPreferredType()==LLFolderType::FT_CURRENT_OUTFIT); + const BOOL move_is_into_landmarks = (dest_id == landmarks_id) || model->isObjectDescendentOf(dest_id, landmarks_id); + const BOOL move_is_into_lost_and_found = model->isObjectDescendentOf(dest_id, lost_and_found_id); + + //-------------------------------------------------------------------------------- + // Determine if folder can be moved. + // + + BOOL is_movable = TRUE; + + if (is_movable && (marketplacelistings_id == cat_id)) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipOutboxCannotMoveRoot"); + } + if (is_movable && move_is_from_marketplacelistings) + //&& LLMarketplaceData::instance().getActivationState(cat_id)) + { + // If the incoming folder is listed and active (and is therefore either the listing or the version folder), + // then moving is *not* allowed + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipOutboxDragActive"); + } + if (is_movable && (dest_id == cat_id)) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipDragOntoSelf"); + } + if (is_movable && (model->isObjectDescendentOf(dest_id, cat_id))) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipDragOntoOwnChild"); + } + if (is_movable && LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + is_movable = FALSE; + // tooltip? + } + + U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); + if (is_movable && move_is_into_outfit) + { + if (dest_id == my_outifts_id) + { + if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutfitNotInInventory"); + is_movable = false; + } + else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear)) + { + is_movable = true; + } + else + { + tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit"); + is_movable = false; + } + } + else if(dest_cat && dest_cat->getPreferredType() == LLFolderType::FT_NONE) + { + is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)); + } + else + { + is_movable = false; + } + } + if(is_movable && move_is_into_current_outfit && is_link) + { + is_movable = FALSE; + } + if (is_movable && move_is_into_lost_and_found) + { + is_movable = FALSE; + } + if (is_movable && (dest_id == model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE))) + { + is_movable = FALSE; + // tooltip? + } + if (is_movable && (dest_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)) + { + // One cannot move a folder into a stock folder + is_movable = FALSE; + // tooltip? + } + + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + if (is_movable) + { + model->collectDescendents(cat_id, descendent_categories, descendent_items, FALSE); + for (S32 i=0; i < descendent_categories.size(); ++i) + { + LLInventoryCategory* category = descendent_categories[i]; + if(LLFolderType::lookupIsProtectedType(category->getPreferredType())) + { + // Can't move "special folders" (e.g. Textures Folder). + is_movable = FALSE; + break; + } + } + } + if (is_movable + && move_is_into_current_outfit + && descendent_items.size() > max_items_to_wear) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + + if (items.size() > max_items_to_wear) + { + // Can't move 'large' folders into current outfit: MAINT-4086 + is_movable = FALSE; + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", max_items_to_wear); + tooltip_msg = LLTrans::getString("TooltipTooManyWearables",args); + } + } + if (is_movable && move_is_into_trash) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLInventoryItem* item = descendent_items[i]; + if (get_is_item_worn(item->getUUID())) + { + is_movable = FALSE; + break; // It's generally movable, but not into the trash. + } + } + } + if (is_movable && move_is_into_landmarks) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLViewerInventoryItem* item = descendent_items[i]; + + // Don't move anything except landmarks and categories into Landmarks folder. + // We use getType() instead of getActua;Type() to allow links to landmarks and folders. + if (LLAssetType::AT_LANDMARK != item->getType() && LLAssetType::AT_CATEGORY != item->getType()) + { + is_movable = FALSE; + break; // It's generally movable, but not into Landmarks. + } + } + } + + if (is_movable && move_is_into_marketplacelistings) + { + const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, dest_id); + LLViewerInventoryCategory * dest_folder = dest_cat; + S32 bundle_size = (drop ? 1 : LLToolDragAndDrop::instance().getCargoCount()); + is_movable = can_move_folder_to_marketplace(master_folder, dest_folder, inv_cat, tooltip_msg, bundle_size); + } + + // + //-------------------------------------------------------------------------------- + + accept = is_movable; + + if (accept && drop) + { + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + //disable dropping in or out of marketplace for now + return FALSE; + } + // Look for any gestures and deactivate them + if (move_is_into_trash) + { + for (S32 i=0; i < descendent_items.size(); i++) + { + LLInventoryItem* item = descendent_items[i]; + if (item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(item->getUUID())) + { + LLGestureMgr::instance().deactivateGesture(item->getUUID()); + } + } + } + + if (dest_id == my_outifts_id) + { + // Category can contains objects, + // create a new folder and populate it with links to original objects + dropToMyOutfits(inv_cat); + } + // if target is current outfit folder we use link + else if (move_is_into_current_outfit && + (inv_cat->getPreferredType() == LLFolderType::FT_NONE || + inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + // traverse category and add all contents to currently worn. + BOOL append = true; + LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, false, append); + } + else if (move_is_into_marketplacelistings) + { + //move_folder_to_marketplacelistings(inv_cat, dest_id); + } + else + { + if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX))) + { + set_dad_inbox_object(cat_id); + } + + // Reparent the folder and restamp children if it's moving + // into trash. + gInventory.changeCategoryParent( + (LLViewerInventoryCategory*)inv_cat, + dest_id, + move_is_into_trash); + } + if (move_is_from_marketplacelistings) + { + //disable dropping in or out of marketplace for now + return FALSE; + + // If we are moving a folder at the listing folder level (i.e. its parent is the marketplace listings folder) + /*if (from_folder_uuid == marketplacelistings_id) + { + // Clear the folder from the marketplace in case it is a listing folder + if (LLMarketplaceData::instance().isListed(cat_id)) + { + LLMarketplaceData::instance().clearListing(cat_id); + } + } + else + { + // If we move from within an active (listed) listing, checks that it's still valid, if not, unlist + LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) + { + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + } + ); + } + // In all cases, update the listing we moved from so suffix are updated + update_marketplace_category(from_folder_uuid); + }*/ + } + } + } + else if (LLToolDragAndDrop::SOURCE_WORLD == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else + { + accept = move_inv_category_world_to_agent(cat_id, dest_id, drop); + } + } + else if (LLToolDragAndDrop::SOURCE_LIBRARY == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else + { + // Accept folders that contain complete outfits. + accept = move_is_into_current_outfit && LLAppearanceMgr::instance().getCanMakeFolderIntoOutfit(cat_id); + } + + if (accept && drop) + { + LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, true, false); + } + } + + return accept; +} + +void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id) +{ + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_source_id, categories, items); + + LLInventoryObject::const_object_list_t link_array; + + + LLInventoryModel::item_array_t::iterator iter = items->begin(); + LLInventoryModel::item_array_t::iterator end = items->end(); + while (iter!=end) + { + const LLViewerInventoryItem* item = (*iter); + // By this point everything is supposed to be filtered, + // but there was a delay to create folder so something could have changed + LLInventoryType::EType inv_type = item->getInventoryType(); + if ((inv_type == LLInventoryType::IT_WEARABLE) || + (inv_type == LLInventoryType::IT_GESTURE) || + (inv_type == LLInventoryType::IT_ATTACHMENT) || + (inv_type == LLInventoryType::IT_OBJECT) || + (inv_type == LLInventoryType::IT_SNAPSHOT) || + (inv_type == LLInventoryType::IT_TEXTURE)) + { + link_array.push_back(LLConstPointer<LLInventoryObject>(item)); + } + iter++; + } + + if (!link_array.empty()) + { + LLPointer<LLInventoryCallback> cb = NULL; + link_inventory_array(cat_dest_id, link_array, cb); + } +} + +void dropToMyOutfits(LLInventoryCategory* inv_cat) +{ + // make a folder in the My Outfits directory. + const LLUUID dest_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + // Note: creation will take time, so passing folder id to callback is slightly unreliable, + // but so is collecting and passing descendants' ids + inventory_func_type func = boost::bind(&outfitFolderCreatedCallback, inv_cat->getUUID(), _1); + gInventory.createNewCategory(dest_id, LLFolderType::FT_OUTFIT, inv_cat->getName(), func, inv_cat->getThumbnailUUID()); +} + diff --git a/indra/newview/llinventorygallery.h b/indra/newview/llinventorygallery.h new file mode 100644 index 0000000000..9b3f12701f --- /dev/null +++ b/indra/newview/llinventorygallery.h @@ -0,0 +1,419 @@ +/** + * @file llinventorygallery.h + * @brief LLInventoryGallery class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYGALLERY_H +#define LL_LLINVENTORYGALLERY_H + +#include "llgesturemgr.h" +#include "lllistcontextmenu.h" +#include "llpanel.h" +#include "llinventoryfilter.h" +#include "llinventoryobserver.h" +#include "llinventorymodel.h" + +class LLInventoryCategoriesObserver; +class LLInventoryGalleryItem; +class LLScrollContainer; +class LLTextBox; +class LLThumbnailsObserver; +class LLGalleryGestureObserver; + +class LLInventoryGalleryContextMenu; + +typedef boost::function<void()> callback_t; + +class LLInventoryGallery : public LLPanel, public LLEditMenuHandler +{ +public: + + typedef boost::signals2::signal<void(const LLUUID&)> selection_change_signal_t; + typedef boost::function<void(const LLUUID&)> selection_change_callback_t; + typedef std::deque<LLUUID> selection_deque; + + struct Params + : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<S32> row_panel_height; + Optional<S32> row_panel_width_factor; + Optional<S32> gallery_width_factor; + Optional<S32> vertical_gap; + Optional<S32> horizontal_gap; + Optional<S32> item_width; + Optional<S32> item_height; + Optional<S32> item_horizontal_gap; + Optional<S32> items_in_row; + + Params(); + }; + + static const LLInventoryGallery::Params& getDefaultParams(); + + LLInventoryGallery(const LLInventoryGallery::Params& params = getDefaultParams()); + ~LLInventoryGallery(); + + BOOL postBuild() override; + void initGallery(); + void draw() override; + void onVisibilityChange(BOOL new_visibility) override; + BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override; + void startDrag(); + BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + BOOL handleKeyHere(KEY key, MASK mask) override; + void moveUp(MASK mask); + void moveDown(MASK mask); + void moveLeft(MASK mask); + void moveRight(MASK mask); + void toggleSelectionRange(S32 start_idx, S32 end_idx); + void toggleSelectionRangeFromLast(const LLUUID target); + + void onFocusLost() override; + void onFocusReceived() override; + + void setFilterSubString(const std::string& string); + std::string getFilterSubString() { return mFilterSubString; } + LLInventoryFilter& getFilter() const { return *mFilter; } + bool checkAgainstFilterType(const LLUUID& object_id); + + void getCurrentCategories(uuid_vec_t& vcur); + bool updateAddedItem(LLUUID item_id); // returns true if added item is visible + void updateRemovedItem(LLUUID item_id); + void updateChangedItemName(LLUUID item_id, std::string name); + void updateItemThumbnail(LLUUID item_id); + void updateWornItem(LLUUID item_id, bool is_worn); + + void updateMessageVisibility(); + + void setRootFolder(const LLUUID cat_id); + void updateRootFolder(); + LLUUID getRootFolder() { return mFolderID; } + bool isRootDirty() { return mRootDirty; } + boost::signals2::connection setRootChangedCallback(callback_t cb); + void onForwardFolder(); + void onBackwardFolder(); + void clearNavigationHistory(); + bool isBackwardAvailable(); + bool isForwardAvailable(); + + void setNavBackwardList(std::list<LLUUID> backward_list) { mBackwardFolders = backward_list; } + void setNavForwardList(std::list<LLUUID> forward_list) { mForwardFolders = forward_list; } + std::list<LLUUID> getNavBackwardList() { return mBackwardFolders; } + std::list<LLUUID> getNavForwardList() { return mForwardFolders; } + + LLUUID getOutfitImageID(LLUUID outfit_id); + + void refreshList(const LLUUID& category_id); + void onCOFChanged(); + void onGesturesChanged(); + void computeDifference(const LLInventoryModel::cat_array_t vcats, const LLInventoryModel::item_array_t vitems, uuid_vec_t& vadded, uuid_vec_t& vremoved); + + void deselectItem(const LLUUID& category_id); + void clearSelection(); + void changeItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + void addItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + bool toggleItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + void scrollToShowItem(const LLUUID& item_id); + void signalSelectionItemID(const LLUUID& category_id); + boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); + LLUUID getFirstSelectedItemID(); + + void setSearchType(LLInventoryFilter::ESearchType type); + LLInventoryFilter::ESearchType getSearchType() { return mSearchType; } + + bool areViewsInitialized(); + bool hasDescendents(const LLUUID& cat_id); + bool hasVisibleItems(); + void handleModifiedFilter(); + LLScrollContainer* getScrollableContainer() { return mScrollPanel; } + LLInventoryGalleryItem* getFirstSelectedItem(); + + // Copy & paste (LLEditMenuHandler) + void copy() override; + BOOL canCopy() const override; + + void cut() override; + BOOL canCut() const override; + + void paste() override; + BOOL canPaste() const override; + + // Copy & paste & delete + static void onDelete(const LLSD& notification, const LLSD& response, const selection_deque selected_ids); + void deleteSelection(); + bool canDeleteSelection(); + void pasteAsLink(); + + void setSortOrder(U32 order, bool update = false); + U32 getSortOrder() { return mSortOrder; }; + + void claimEditHandler(); + void resetEditHandler(); + static bool isItemCopyable(const LLUUID & item_id); + + BOOL baseHandleDragAndDrop(LLUUID dest_id, BOOL drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); + + void showContextMenu(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& item_id); + +protected: + void paste(const LLUUID& dest, + std::vector<LLUUID>& objects, + bool is_cut_mode, + const LLUUID& marketplacelistings_id); + void pasteAsLink(const LLUUID& dest, + std::vector<LLUUID>& objects, + const LLUUID& current_outfit_id, + const LLUUID& marketplacelistings_id, + const LLUUID& my_outifts_id); + + bool applyFilter(LLInventoryGalleryItem* item, const std::string& filter_substring); + bool checkAgainstFilters(LLInventoryGalleryItem* item, const std::string& filter_substring); + static void onIdle(void* userdata); + void dirtyRootFolder(); + + LLInventoryCategoriesObserver* mCategoriesObserver; + LLThumbnailsObserver* mThumbnailsObserver; + LLGalleryGestureObserver* mGestureObserver; + LLInventoryObserver* mInventoryObserver; + selection_deque mSelectedItemIDs; + selection_deque mItemsToSelect; + LLUUID mLastInteractedUUID; + bool mIsInitialized; + bool mRootDirty; + + selection_change_signal_t mSelectionChangeSignal; + boost::signals2::signal<void()> mRootChangedSignal; + LLUUID mFolderID; + std::list<LLUUID> mBackwardFolders; + std::list<LLUUID> mForwardFolders; + +private: + void addToGallery(LLInventoryGalleryItem* item); + void removeFromGalleryLast(LLInventoryGalleryItem* item, bool needs_reshape = true); + void removeFromGalleryMiddle(LLInventoryGalleryItem* item); + LLPanel* addLastRow(); + void removeLastRow(); + void moveRowUp(int row); + void moveRowDown(int row); + void moveRow(int row, int pos); + LLPanel* addToRow(LLPanel* row_stack, LLInventoryGalleryItem* item, int pos, int hgap); + void removeFromLastRow(LLInventoryGalleryItem* item); + void reArrangeRows(S32 row_diff = 0); + bool updateRowsIfNeeded(); + void updateGalleryWidth(); + + LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn); + + void buildGalleryPanel(int row_count); + void reshapeGalleryPanel(int row_count); + LLPanel* buildItemPanel(int left); + LLPanel* buildRowPanel(int left, int bottom); + void moveRowPanel(LLPanel* stack, int left, int bottom); + + std::vector<LLPanel*> mRowPanels; + std::vector<LLPanel*> mItemPanels; + std::vector<LLPanel*> mUnusedRowPanels; + std::vector<LLPanel*> mUnusedItemPanels; + std::vector<LLInventoryGalleryItem*> mItems; + std::vector<LLInventoryGalleryItem*> mHiddenItems; + LLScrollContainer* mScrollPanel; + LLPanel* mGalleryPanel; + LLPanel* mLastRowPanel; + LLTextBox* mMessageTextBox; + int mRowCount; + int mItemsAddedCount; + bool mGalleryCreated; + bool mNeedsArrange; + + /* Params */ + int mRowPanelHeight; + int mVerticalGap; + int mHorizontalGap; + int mItemWidth; + int mItemHeight; + int mItemHorizontalGap; + int mItemsInRow; + int mRowPanelWidth; + int mGalleryWidth; + int mRowPanWidthFactor; + int mGalleryWidthFactor; + + LLInventoryGalleryContextMenu* mInventoryGalleryMenu; + LLInventoryGalleryContextMenu* mRootGalleryMenu; + std::string mFilterSubString; + LLInventoryFilter* mFilter; + U32 mSortOrder; + + typedef std::map<LLUUID, LLInventoryGalleryItem*> gallery_item_map_t; + gallery_item_map_t mItemMap; + uuid_vec_t mCOFLinkedItems; + uuid_vec_t mActiveGestures; + uuid_set_t mItemBuildQuery; + std::map<LLInventoryGalleryItem*, S32> mItemIndexMap; + std::map<S32, LLInventoryGalleryItem*> mIndexToItemMap; + + LLInventoryFilter::ESearchType mSearchType; + std::string mUsername; +}; + +class LLInventoryGalleryItem : public LLPanel +{ +public: + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + {}; + + enum EInventorySortGroup + { + SG_SYSTEM_FOLDER, + SG_TRASH_FOLDER, + SG_NORMAL_FOLDER, + SG_ITEM + }; + + LLInventoryGalleryItem(const Params& p); + virtual ~LLInventoryGalleryItem(); + + BOOL postBuild(); + void draw(); + BOOL handleMouseDown(S32 x, S32 y, MASK mask); + BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + BOOL handleMouseUp(S32 x, S32 y, MASK mask); + BOOL handleHover(S32 x, S32 y, MASK mask); + BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + BOOL handleKeyHere(KEY key, MASK mask); + + void onFocusLost(); + void onFocusReceived(); + + LLFontGL* getTextFont(); + + void setItemName(std::string name); + bool isSelected() { return mSelected; } + void setSelected(bool value); + void setWorn(bool value); + void setUUID(LLUUID id) {mUUID = id;} + LLUUID getUUID() { return mUUID;} + + void setAssetIDStr(std::string asset_id) {mAssetIDStr = asset_id;} + std::string getAssetIDStr() { return mAssetIDStr;} + void setDescription(std::string desc) {mDesc = desc;} + std::string getDescription() { return mDesc;} + void setCreatorName(std::string name) {mCreatorName = name;} + std::string getCreatorName() { return mCreatorName;} + void setCreationDate(time_t date) {mCreationDate = date;} + time_t getCreationDate() { return mCreationDate;} + + std::string getItemName() {return mItemName;} + std::string getItemNameSuffix() {return mPermSuffix + mWornSuffix;} + bool isDefaultImage() {return mDefaultImage;} + + bool isHidden() {return mHidden;} + void setHidden(bool hidden) {mHidden = hidden;} + + void setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link); + LLAssetType::EType getAssetType() { return mType; } + void setThumbnail(LLUUID id); + void setGallery(LLInventoryGallery* gallery) { mGallery = gallery; } + bool isFolder() { return mIsFolder; } + bool isLink() { return mIsLink; } + EInventorySortGroup getSortGroup() { return mSortGroup; } + + void updateNameText(); + +private: + bool isFadeItem(); + + LLUUID mUUID; + LLTextBox* mNameText; + LLPanel* mTextBgPanel; + bool mSelected; + bool mWorn; + bool mDefaultImage; + bool mHidden; + bool mIsFolder; + bool mIsLink; + S32 mCutGeneration; + bool mSelectedForCut; + + std::string mAssetIDStr; + std::string mDesc; + std::string mCreatorName; + time_t mCreationDate; + + EInventorySortGroup mSortGroup; + LLAssetType::EType mType; + std::string mItemName; + std::string mWornSuffix; + std::string mPermSuffix; + LLInventoryGallery* mGallery; +}; + +class LLThumbnailsObserver : public LLInventoryObserver +{ +public: + LLThumbnailsObserver(){}; + + virtual void changed(U32 mask); + bool addItem(const LLUUID& obj_id, callback_t cb); + void removeItem(const LLUUID& obj_id); + +protected: + + struct LLItemData + { + LLItemData(const LLUUID& obj_id, const LLUUID& thumbnail_id, callback_t cb) + : mItemID(obj_id) + , mCallback(cb) + , mThumbnailID(thumbnail_id) + {} + + callback_t mCallback; + LLUUID mItemID; + LLUUID mThumbnailID; + }; + + typedef std::map<LLUUID, LLItemData> item_map_t; + typedef item_map_t::value_type item_map_value_t; + item_map_t mItemMap; +}; + +class LLGalleryGestureObserver : public LLGestureManagerObserver +{ +public: + LLGalleryGestureObserver(LLInventoryGallery* gallery) : mGallery(gallery) {} + virtual ~LLGalleryGestureObserver() {} + virtual void changed() { mGallery->onGesturesChanged(); } + +private: + LLInventoryGallery* mGallery; +}; + +#endif diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp new file mode 100644 index 0000000000..5f4b816b99 --- /dev/null +++ b/indra/newview/llinventorygallerymenu.cpp @@ -0,0 +1,714 @@ +/** + * @file llinventorygallerymenu.cpp + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinventorygallery.h" +#include "llinventorygallerymenu.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llavataractions.h" +#include "llclipboard.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterworldmap.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "lllandmarkactions.h" +#include "llmarketplacefunctions.h" +#include "llmenugl.h" +#include "llnotificationsutil.h" +#include "llpreviewtexture.h" +#include "lltrans.h" +#include "llviewerfoldertype.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" + +LLContextMenu* LLInventoryGalleryContextMenu::createMenu() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryGalleryContextMenu::doToSelected, this, _2)); + registrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::fileUploadLocation, this, _2)); + registrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); + registrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); + + std::set<LLUUID> uuids(mUUIDs.begin(), mUUIDs.end()); + registrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, uuids, gFloaterView->getParentFloater(mGallery))); + + enable_registrar.add("Inventory.CanSetUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::canSetUploadLocation, this, _2)); + + LLContextMenu* menu = createFromFile("menu_gallery_inventory.xml"); + + updateMenuItemsVisibility(menu); + + return menu; +} + +void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata) +{ + std::string action = userdata.asString(); + LLInventoryObject* obj = gInventory.getObject(mUUIDs.front()); + if(!obj) return; + + if ("open_selected_folder" == action) + { + mGallery->setRootFolder(mUUIDs.front()); + } + else if ("open_in_new_window" == action) + { + new_folder_window(mUUIDs.front()); + } + else if ("properties" == action) + { + show_item_profile(mUUIDs.front()); + } + else if ("restore" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id); + if (cat) + { + const LLUUID new_parent = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(cat->getType())); + // do not restamp children on restore + gInventory.changeCategoryParent(cat, new_parent, false); + } + else + { + LLViewerInventoryItem* item = gInventory.getItem(selected_id); + if (item) + { + bool is_snapshot = (item->getInventoryType() == LLInventoryType::IT_SNAPSHOT); + + const LLUUID new_parent = gInventory.findCategoryUUIDForType(is_snapshot ? LLFolderType::FT_SNAPSHOT_CATEGORY : LLFolderType::assetTypeToFolderType(item->getType())); + // do not restamp children on restore + gInventory.changeItemParent(item, new_parent, false); + } + } + } + } + else if ("copy_uuid" == action) + { + LLViewerInventoryItem* item = gInventory.getItem(mUUIDs.front()); + if(item) + { + LLUUID asset_id = item->getProtectedAssetUUID(); + std::string buffer; + asset_id.toString(buffer); + + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(buffer)); + } + } + else if ("purge" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + remove_inventory_object(selected_id, NULL); + } + } + else if ("goto" == action) + { + show_item_original(mUUIDs.front()); + } + else if ("thumbnail" == action) + { + LLSD data(mUUIDs.front()); + LLFloaterReg::showInstance("change_item_thumbnail", data); + } + else if ("cut" == action) + { + if (mGallery->canCut()) + { + mGallery->cut(); + } + } + else if ("paste" == action) + { + if (mGallery->canPaste()) + { + mGallery->paste(); + } + } + else if ("delete" == action) + { + mGallery->deleteSelection(); + } + else if ("copy" == action) + { + if (mGallery->canCopy()) + { + mGallery->copy(); + } + } + else if ("paste_link" == action) + { + mGallery->pasteAsLink(); + } + else if ("rename" == action) + { + rename(mUUIDs.front()); + } + else if ("open" == action || "open_original" == action) + { + LLViewerInventoryItem* item = gInventory.getItem(mUUIDs.front()); + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(), mUUIDs.front(), &gInventory); + } + } + else if ("ungroup_folder_items" == action) + { + ungroup_folder_items(mUUIDs.front()); + } + else if ("take_off" == action || "detach" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLAppearanceMgr::instance().removeItemFromAvatar(selected_id); + } + } + else if ("wear_add" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLAppearanceMgr::instance().wearItemOnAvatar(selected_id, true, false); // Don't replace if adding. + } + } + else if ("wear" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLAppearanceMgr::instance().wearItemOnAvatar(selected_id, true, true); + } + } + else if ("activate" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLGestureMgr::instance().activateGesture(selected_id); + + LLViewerInventoryItem* item = gInventory.getItem(selected_id); + if (!item) return; + + gInventory.updateItem(item); + } + gInventory.notifyObservers(); + } + else if ("deactivate" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLGestureMgr::instance().deactivateGesture(selected_id); + + LLViewerInventoryItem* item = gInventory.getItem(selected_id); + if (!item) return; + + gInventory.updateItem(item); + } + gInventory.notifyObservers(); + } + else if ("replace_links" == action) + { + LLFloaterReg::showInstance("linkreplace", LLSD(mUUIDs.front())); + } + else if ("copy_slurl" == action) + { + boost::function<void(LLLandmark*)> copy_slurl_cb = [](LLLandmark* landmark) + { + LLVector3d global_pos; + landmark->getGlobalPos(global_pos); + boost::function<void(std::string& slurl)> copy_slurl_to_clipboard_cb = [](const std::string& slurl) + { + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); + LLSD args; + args["SLURL"] = slurl; + LLNotificationsUtil::add("CopySLURL", args); + }; + LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true); + }; + LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), copy_slurl_cb); + if (landmark) + { + copy_slurl_cb(landmark); + } + } + else if ("about" == action) + { + LLSD key; + key["type"] = "landmark"; + key["id"] = mUUIDs.front(); + LLFloaterSidePanelContainer::showPanel("places", key); + } + else if ("show_on_map" == action) + { + boost::function<void(LLLandmark*)> show_on_map_cb = [](LLLandmark* landmark) + { + LLVector3d landmark_global_pos; + if (landmark->getGlobalPos(landmark_global_pos)) + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!landmark_global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(landmark_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + } + }; + LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), show_on_map_cb); + if(landmark) + { + show_on_map_cb(landmark); + } + } + else if ("save_as" == action) + { + LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance<LLPreviewTexture>("preview_texture", mUUIDs.front()); + if (preview_texture) + { + preview_texture->openToSave(); + preview_texture->saveAs(); + } + } +} + +void LLInventoryGalleryContextMenu::rename(const LLUUID& item_id) +{ + LLInventoryObject* obj = gInventory.getObject(item_id); + if (!obj) return; + + LLSD args; + args["NAME"] = obj->getName(); + + LLSD payload; + payload["id"] = mUUIDs.front(); + + LLNotificationsUtil::add("RenameItem", args, payload, boost::bind(onRename, _1, _2)); +} + +void LLInventoryGalleryContextMenu::onRename(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + std::string new_name = response["new_name"].asString(); + LLStringUtil::trim(new_name); + if (!new_name.empty()) + { + LLUUID id = notification["payload"]["id"].asUUID(); + + LLViewerInventoryCategory* cat = gInventory.getCategory(id); + if(cat && (cat->getName() != new_name)) + { + LLSD updates; + updates["name"] = new_name; + update_inventory_category(cat->getUUID(),updates, NULL); + return; + } + + LLViewerInventoryItem* item = gInventory.getItem(id); + if(item && (item->getName() != new_name)) + { + LLSD updates; + updates["name"] = new_name; + update_inventory_item(item->getUUID(),updates, NULL); + } + } +} + +void LLInventoryGalleryContextMenu::fileUploadLocation(const LLSD& userdata) +{ + const std::string param = userdata.asString(); + if (param == "model") + { + gSavedPerAccountSettings.setString("ModelUploadFolder", mUUIDs.front().asString()); + } + else if (param == "texture") + { + gSavedPerAccountSettings.setString("TextureUploadFolder", mUUIDs.front().asString()); + } + else if (param == "sound") + { + gSavedPerAccountSettings.setString("SoundUploadFolder", mUUIDs.front().asString()); + } + else if (param == "animation") + { + gSavedPerAccountSettings.setString("AnimationUploadFolder", mUUIDs.front().asString()); + } +} + +bool LLInventoryGalleryContextMenu::canSetUploadLocation(const LLSD& userdata) +{ + if (mUUIDs.size() != 1) + { + return false; + } + LLInventoryCategory* cat = gInventory.getCategory(mUUIDs.front()); + if (!cat) + { + return false; + } + return true; +} + +bool is_inbox_folder(LLUUID item_id) +{ + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + + if (inbox_id.isNull()) + { + return false; + } + + return gInventory.isObjectDescendentOf(item_id, inbox_id); +} + +void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* menu) +{ + LLUUID selected_id = mUUIDs.front(); + LLInventoryObject* obj = gInventory.getObject(selected_id); + if (!obj) + { + return; + } + + std::vector<std::string> items; + std::vector<std::string> disabled_items; + + bool is_agent_inventory = gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID()); + bool is_link = obj->getIsLinkType(); + bool is_folder = (obj->getType() == LLAssetType::AT_CATEGORY); + bool is_cof = LLAppearanceMgr::instance().getIsInCOF(selected_id); + bool is_inbox = is_inbox_folder(selected_id); + bool is_trash = (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH)); + bool is_in_trash = gInventory.isObjectDescendentOf(selected_id, gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH)); + bool is_lost_and_found = (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + bool is_outfits= (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); + //bool is_favorites= (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE)); + + bool is_system_folder = false; + LLFolderType::EType folder_type(LLFolderType::FT_NONE); + bool has_children = false; + bool is_full_perm_item = false; + bool is_copyable = false; + LLViewerInventoryItem* selected_item = gInventory.getItem(selected_id); + + if(is_folder) + { + LLInventoryCategory* category = gInventory.getCategory(selected_id); + if (category) + { + folder_type = category->getPreferredType(); + is_system_folder = LLFolderType::lookupIsProtectedType(folder_type); + has_children = (gInventory.categoryHasChildren(selected_id) != LLInventoryModel::CHILDREN_NO); + } + } + else + { + if (selected_item) + { + is_full_perm_item = selected_item->getIsFullPerm(); + is_copyable = selected_item->getPermissions().allowCopyBy(gAgent.getID()); + } + } + + if(!is_link) + { + items.push_back(std::string("thumbnail")); + if (!is_agent_inventory || (is_in_trash && !is_trash)) + { + disabled_items.push_back(std::string("thumbnail")); + } + } + + if (is_folder) + { + if(!isRootFolder()) + { + items.push_back(std::string("Copy Separator")); + + items.push_back(std::string("open_in_current_window")); + items.push_back(std::string("open_in_new_window")); + items.push_back(std::string("Open Folder Separator")); + } + } + else + { + if (is_agent_inventory && (obj->getType() != LLAssetType::AT_LINK_FOLDER)) + { + items.push_back(std::string("Replace Links")); + } + if (obj->getType() == LLAssetType::AT_LANDMARK) + { + items.push_back(std::string("Landmark Separator")); + items.push_back(std::string("url_copy")); + items.push_back(std::string("About Landmark")); + items.push_back(std::string("show_on_map")); + } + } + + if(is_trash) + { + items.push_back(std::string("Empty Trash")); + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(selected_id, cat_array, item_array); + if (0 == cat_array->size() && 0 == item_array->size()) + { + disabled_items.push_back(std::string("Empty Trash")); + } + } + else if(is_in_trash) + { + if (is_link) + { + items.push_back(std::string("Find Original")); + if (LLAssetType::lookupIsLinkType(obj->getType())) + { + disabled_items.push_back(std::string("Find Original")); + } + } + items.push_back(std::string("Purge Item")); + if (is_folder && !get_is_category_removable(&gInventory, selected_id)) + { + disabled_items.push_back(std::string("Purge Item")); + } + items.push_back(std::string("Restore Item")); + } + else + { + if(can_share_item(selected_id)) + { + items.push_back(std::string("Share")); + } + if (LLClipboard::instance().hasContents() && is_agent_inventory && !is_cof && !is_inbox_folder(selected_id)) + { + items.push_back(std::string("Paste")); + + static LLCachedControl<bool> inventory_linking(gSavedSettings, "InventoryLinking", true); + if (inventory_linking) + { + items.push_back(std::string("Paste As Link")); + } + } + if (is_folder && is_agent_inventory) + { + if (!is_cof && (folder_type != LLFolderType::FT_OUTFIT) && !is_outfits && !is_inbox_folder(selected_id)) + { + if (!gInventory.isObjectDescendentOf(selected_id, gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD)) && !isRootFolder()) + { + items.push_back(std::string("New Folder")); + } + items.push_back(std::string("upload_def")); + } + + if(is_outfits && !isRootFolder()) + { + items.push_back(std::string("New Outfit")); + } + + items.push_back(std::string("Subfolder Separator")); + if (!is_system_folder && !isRootFolder()) + { + if(has_children && (folder_type != LLFolderType::FT_OUTFIT)) + { + items.push_back(std::string("Ungroup folder items")); + } + items.push_back(std::string("Cut")); + items.push_back(std::string("Delete")); + if(!get_is_category_removable(&gInventory, selected_id)) + { + disabled_items.push_back(std::string("Delete")); + disabled_items.push_back(std::string("Cut")); + } + + if(!is_inbox) + { + items.push_back(std::string("Rename")); + } + } + if(!is_system_folder) + { + items.push_back(std::string("Copy")); + } + } + else if(!is_folder) + { + items.push_back(std::string("Properties")); + items.push_back(std::string("Copy Asset UUID")); + items.push_back(std::string("Copy Separator")); + + bool is_asset_knowable = is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(obj->getType()); + if ( !is_asset_knowable // disable menu item for Inventory items with unknown asset. EXT-5308 + || (! ( is_full_perm_item || gAgent.isGodlike()))) + { + disabled_items.push_back(std::string("Copy Asset UUID")); + } + if(is_agent_inventory) + { + items.push_back(std::string("Cut")); + if (!is_link || !is_cof || !get_is_item_worn(selected_id)) + { + items.push_back(std::string("Delete")); + } + if(!get_is_item_removable(&gInventory, selected_id)) + { + disabled_items.push_back(std::string("Delete")); + disabled_items.push_back(std::string("Cut")); + } + + if (selected_item && (selected_item->getInventoryType() != LLInventoryType::IT_CALLINGCARD) && !is_inbox && selected_item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) + { + items.push_back(std::string("Rename")); + } + } + items.push_back(std::string("Copy")); + if (!is_copyable) + { + disabled_items.push_back(std::string("Copy")); + } + } + if((obj->getType() == LLAssetType::AT_SETTINGS) + || ((obj->getType() <= LLAssetType::AT_GESTURE) + && obj->getType() != LLAssetType::AT_OBJECT + && obj->getType() != LLAssetType::AT_CLOTHING + && obj->getType() != LLAssetType::AT_CATEGORY + && obj->getType() != LLAssetType::AT_LANDMARK + && obj->getType() != LLAssetType::AT_BODYPART)) + { + bool can_open = !LLAssetType::lookupIsLinkType(obj->getType()); + + if (can_open) + { + if (is_link) + items.push_back(std::string("Open Original")); + else + items.push_back(std::string("Open")); + } + else + { + disabled_items.push_back(std::string("Open")); + disabled_items.push_back(std::string("Open Original")); + } + + if(LLAssetType::AT_GESTURE == obj->getType()) + { + items.push_back(std::string("Gesture Separator")); + if(!LLGestureMgr::instance().isGestureActive(selected_id)) + { + items.push_back(std::string("Activate")); + } + else + { + items.push_back(std::string("Deactivate")); + } + } + } + else if(LLAssetType::AT_LANDMARK == obj->getType()) + { + items.push_back(std::string("Landmark Open")); + } + else if (obj->getType() == LLAssetType::AT_OBJECT || obj->getType() == LLAssetType::AT_CLOTHING || obj->getType() == LLAssetType::AT_BODYPART) + { + items.push_back(std::string("Wearable And Object Separator")); + if(obj->getType() == LLAssetType::AT_CLOTHING) + { + items.push_back(std::string("Take Off")); + } + if(get_is_item_worn(selected_id)) + { + if(obj->getType() == LLAssetType::AT_OBJECT) + { + items.push_back(std::string("Detach From Yourself")); + } + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + } + else + { + if(obj->getType() == LLAssetType::AT_OBJECT) + { + items.push_back(std::string("Wearable Add")); + } + items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Take Off")); + } + + if (!gAgentAvatarp->canAttachMoreObjects() && (obj->getType() == LLAssetType::AT_OBJECT)) + { + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + } + if (selected_item && (obj->getType() != LLAssetType::AT_OBJECT) && LLWearableType::getInstance()->getAllowMultiwear(selected_item->getWearableType())) + { + items.push_back(std::string("Wearable Add")); + if (!gAgentWearables.canAddWearable(selected_item->getWearableType())) + { + disabled_items.push_back(std::string("Wearable Add")); + } + } + } + if(obj->getType() == LLAssetType::AT_TEXTURE) + { + items.push_back(std::string("Save As")); + bool can_copy = selected_item && selected_item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); + if (!can_copy) + { + disabled_items.push_back(std::string("Save As")); + } + } + if (is_link) + { + items.push_back(std::string("Find Original")); + if (LLAssetType::lookupIsLinkType(obj->getType())) + { + disabled_items.push_back(std::string("Find Original")); + } + } + if (is_lost_and_found) + { + items.push_back(std::string("Empty Lost And Found")); + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(selected_id, cat_array, item_array); + // Enable Empty menu item only when there is something to act upon. + if (0 == cat_array->size() && 0 == item_array->size()) + { + disabled_items.push_back(std::string("Empty Lost And Found")); + } + + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("upload_def")); + } + } + + hide_context_entries(*menu, items, disabled_items); +} + diff --git a/indra/newview/llinventorygallerymenu.h b/indra/newview/llinventorygallerymenu.h new file mode 100644 index 0000000000..7c3545432b --- /dev/null +++ b/indra/newview/llinventorygallerymenu.h @@ -0,0 +1,62 @@ +/** + * @file llinventorygallerymenu.h + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYGALLERYMENU_H +#define LL_LLINVENTORYGALLERYMENU_H + +#include "lllistcontextmenu.h" + +class LLInventoryGalleryContextMenu : public LLListContextMenu +{ +public: + LLInventoryGalleryContextMenu(LLInventoryGallery* gallery) + : LLListContextMenu(), + mGallery(gallery), + mRootFolder(false){} + /*virtual*/ LLContextMenu* createMenu(); + + bool isRootFolder() { return mRootFolder; } + void setRootFolder(bool is_root) { mRootFolder = is_root; } + void doToSelected(const LLSD& userdata); + void rename(const LLUUID& item_id); + +protected: + //virtual void buildContextMenu(class LLMenuGL& menu, U32 flags); + void updateMenuItemsVisibility(LLContextMenu* menu); + + void fileUploadLocation(const LLSD& userdata); + bool canSetUploadLocation(const LLSD& userdata); + + static void onRename(const LLSD& notification, const LLSD& response); + +private: + bool enableContextMenuItem(const LLSD& userdata); + bool checkContextMenuItem(const LLSD& userdata); + + LLInventoryGallery* mGallery; + bool mRootFolder; +}; + +#endif diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 9c4e122481..ea771661ec 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -37,9 +37,11 @@ #include "llappearancemgr.h" #include "llavatarnamecache.h" #include "llclipboard.h" +#include "lldispatcher.h" #include "llinventorypanel.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" #include "llinventoryobserver.h" #include "llinventorypanel.h" #include "llfloaterpreviewtrash.h" @@ -49,6 +51,7 @@ #include "llviewercontrol.h" #include "llviewernetwork.h" #include "llpreview.h" +#include "llviewergenericmessage.h" #include "llviewermessage.h" #include "llviewerfoldertype.h" #include "llviewerwindow.h" @@ -74,9 +77,11 @@ // Increment this if the inventory contents change in a non-backwards-compatible way. // For viewer 2, the addition of link items makes a pre-viewer-2 cache incorrect. -const S32 LLInventoryModel::sCurrentInvCacheVersion = 2; +const S32 LLInventoryModel::sCurrentInvCacheVersion = 3; BOOL LLInventoryModel::sFirstTimeInViewer2 = TRUE; +S32 LLInventoryModel::sPendingSystemFolders = 0; + ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs ///---------------------------------------------------------------------------- @@ -133,6 +138,222 @@ bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item) return rv; } +struct InventoryCallbackInfo +{ + InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) : + mCallback(callback), mInvID(inv_id) {} + U32 mCallback; + LLUUID mInvID; +}; + +///---------------------------------------------------------------------------- +/// Class LLDispatchClassifiedClickThrough +///---------------------------------------------------------------------------- + +class LLDispatchBulkUpdateInventory : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + LLSD message; + + // Expect single string parameter in the form of a notation serialized LLSD. + sparam_t::const_iterator it = strings.begin(); + if (it != strings.end()) { + const std::string& llsdRaw = *it++; + std::istringstream llsdData(llsdRaw); + if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) + { + LL_WARNS() << "LLDispatchBulkUpdateInventory: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; + } + } + + LLInventoryModel::update_map_t update; + LLInventoryModel::cat_array_t folders; + LLInventoryModel::item_array_t items; + std::list<InventoryCallbackInfo> cblist; + uuid_vec_t wearable_ids; + + LLSD item_data = message["item_data"]; + if (item_data.isArray()) + { + for (LLSD::array_iterator itd = item_data.beginArray(); itd != item_data.endArray(); ++itd) + { + const LLSD &item(*itd); + + // Agent id probably should be in the root of the message + LLUUID agent_id = item["agent_id"].asUUID(); + if (agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a BulkUpdateInventory for the wrong agent." << LL_ENDL; + return false; + } + + LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem; + titem->unpackMessage(item); + LL_DEBUGS("Inventory") << "unpacked item '" << titem->getName() << "' in " + << titem->getParentUUID() << LL_ENDL; + // callback id might be no longer supported + U32 callback_id = item["callback_id"].asInteger(); + + if (titem->getUUID().notNull()) + { + items.push_back(titem); + cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID())); + if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE) + { + wearable_ids.push_back(titem->getUUID()); + } + + // examine update for changes. + LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); + if (itemp) + { + if (titem->getParentUUID() == itemp->getParentUUID()) + { + update[titem->getParentUUID()]; + } + else + { + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; + } + } + else + { + LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); + if (folderp) + { + ++update[titem->getParentUUID()]; + } + } + } + else + { + cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null)); + } + } + } + + LLSD folder_data = message["folder_data"]; + if (folder_data.isArray()) + { + for (LLSD::array_iterator itd = folder_data.beginArray(); itd != folder_data.endArray(); ++itd) + { + const LLSD &folder(*itd); + + LLPointer<LLViewerInventoryCategory> tfolder = new LLViewerInventoryCategory(gAgent.getID()); + tfolder->unpackMessage(folder); + + LL_DEBUGS("Inventory") << "unpacked folder '" << tfolder->getName() << "' (" + << tfolder->getUUID() << ") in " << tfolder->getParentUUID() + << LL_ENDL; + + // If the folder is a listing or a version folder, all we need to do is update the SLM data + int depth_folder = depth_nesting_in_marketplace(tfolder->getUUID()); + if ((depth_folder == 1) || (depth_folder == 2)) + { + // Trigger an SLM listing update + LLUUID listing_uuid = (depth_folder == 1 ? tfolder->getUUID() : tfolder->getParentUUID()); + S32 listing_id = LLMarketplaceData::instance().getListingID(listing_uuid); + LLMarketplaceData::instance().getListing(listing_id); + // In that case, there is no item to update so no callback -> we skip the rest of the update + } + else if (tfolder->getUUID().notNull()) + { + folders.push_back(tfolder); + LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); + if (folderp) + { + if (tfolder->getParentUUID() == folderp->getParentUUID()) + { + update[tfolder->getParentUUID()]; + } + else + { + ++update[tfolder->getParentUUID()]; + --update[folderp->getParentUUID()]; + } + } + else + { + // we could not find the folder, so it is probably + // new. However, we only want to attempt accounting + // for the parent if we can find the parent. + folderp = gInventory.getCategory(tfolder->getParentUUID()); + if (folderp) + { + ++update[tfolder->getParentUUID()]; + } + } + } + } + } + + gInventory.accountForUpdate(update); + + for (LLInventoryModel::cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) + { + gInventory.updateCategory(*cit); + } + for (LLInventoryModel::item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) + { + gInventory.updateItem(*iit); + } + gInventory.notifyObservers(); + + /* + Transaction id not included? + + // The incoming inventory could span more than one BulkInventoryUpdate packet, + // so record the transaction ID for this purchase, then wear all clothing + // that comes in as part of that transaction ID. JC + if (LLInventoryState::sWearNewClothing) + { + LLInventoryState::sWearNewClothingTransactionID = tid; + LLInventoryState::sWearNewClothing = FALSE; + } + + if (tid.notNull() && tid == LLInventoryState::sWearNewClothingTransactionID) + { + count = wearable_ids.size(); + for (i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + */ + + if (LLInventoryState::sWearNewClothing && wearable_ids.size() > 0) + { + LLInventoryState::sWearNewClothing = FALSE; + + size_t count = wearable_ids.size(); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + + std::list<InventoryCallbackInfo>::iterator inv_it; + for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it) + { + InventoryCallbackInfo cbinfo = (*inv_it); + gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); + } + return true; + } +}; +static LLDispatchBulkUpdateInventory sBulkUpdateInventory; + ///---------------------------------------------------------------------------- /// Class LLInventoryValidationInfo ///---------------------------------------------------------------------------- @@ -222,6 +443,7 @@ LLInventoryModel::LLInventoryModel() mIsNotifyObservers(FALSE), mModifyMask(LLInventoryObserver::ALL), mChangedItemIDs(), + mBulkFecthCallbackSlot(), mObservers(), mHttpRequestFG(NULL), mHttpRequestBG(NULL), @@ -253,6 +475,11 @@ void LLInventoryModel::cleanupInventory() mObservers.erase(iter); delete observer; } + + if (mBulkFecthCallbackSlot.connected()) + { + mBulkFecthCallbackSlot.disconnect(); + } mObservers.clear(); // Run down HTTP transport @@ -452,6 +679,31 @@ void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, items = get_ptr_in_map(mParentChildItemTree, cat_id); } +void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const +{ + if (cat_array_t* categoriesp = get_ptr_in_map(mParentChildCategoryTree, cat_id)) + { + for (LLViewerInventoryCategory* pFolder : *categoriesp) + { + if (f(pFolder, nullptr)) + { + categories.push_back(pFolder); + } + } + } + + if (item_array_t* itemsp = get_ptr_in_map(mParentChildItemTree, cat_id)) + { + for (LLViewerInventoryItem* pItem : *itemsp) + { + if (f(nullptr, pItem)) + { + items.push_back(pItem); + } + } + } +} + LLInventoryModel::digest_t LLInventoryModel::hashDirectDescendentNames(const LLUUID& cat_id) const { LLInventoryModel::cat_array_t* cat_array; @@ -561,10 +813,77 @@ void LLInventoryModel::consolidateForType(const LLUUID& main_id, LLFolderType::E } } +void LLInventoryModel::ensureCategoryForTypeExists(LLFolderType::EType preferred_type) +{ + LLUUID rv = LLUUID::null; + LLUUID root_id = gInventory.getRootFolderID(); + if (LLFolderType::FT_ROOT_INVENTORY == preferred_type) + { + rv = root_id; + } + else if (root_id.notNull()) + { + cat_array_t* cats = NULL; + cats = get_ptr_in_map(mParentChildCategoryTree, root_id); + if (cats) + { + S32 count = cats->size(); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* p_cat = cats->at(i); + if (p_cat && p_cat->getPreferredType() == preferred_type) + { + const LLUUID& folder_id = cats->at(i)->getUUID(); + if (rv.isNull() || folder_id < rv) + { + rv = folder_id; + } + } + } + } + } + + if (rv.isNull() && root_id.notNull()) + { + + if (isInventoryUsable()) + { + createNewCategory( + root_id, + preferred_type, + LLStringUtil::null, + [preferred_type](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS("Inventory") + << "Failed to create folder of type " << preferred_type + << LL_ENDL; + } + else + { + LL_WARNS("Inventory") << "Created category: " << new_cat_id + << " for type: " << preferred_type << LL_ENDL; + sPendingSystemFolders--; + } + } + ); + } + else + { + LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type + << " because inventory is not usable" << LL_ENDL; + } + } + else + { + sPendingSystemFolders--; + } +} + const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( LLFolderType::EType preferred_type, - bool create_folder, - const LLUUID& root_id) + const LLUUID& root_id) const { LLUUID rv = LLUUID::null; if(LLFolderType::FT_ROOT_INVENTORY == preferred_type) @@ -595,20 +914,15 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( if(rv.isNull() && root_id.notNull() - && create_folder && preferred_type != LLFolderType::FT_MARKETPLACE_LISTINGS && preferred_type != LLFolderType::FT_OUTBOX) { - - if (isInventoryUsable()) - { - return createNewCategory(root_id, preferred_type, LLStringUtil::null); - } - else - { - LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type - << " because inventory is not usable" << LL_ENDL; - } + // if it does not exists, it should either be added + // to createCommonSystemCategories or server should + // have set it + llassert(!isInventoryUsable()); + LL_WARNS("Inventory") << "Tried to find folder, type " << preferred_type + << " but category does not exist" << LL_ENDL; } return rv; } @@ -617,12 +931,12 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( // specifies 'type' as what it defaults to containing. The category is // not necessarily only for that type. *NOTE: This will create a new // inventory category on the fly if one does not exist. -const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type, bool create_folder) +const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type) const { - return findCategoryUUIDForTypeInRoot(preferred_type, create_folder, gInventory.getRootFolderID()); + return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getRootFolderID()); } -const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) +const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const { LLUUID cat_id; switch (preferred_type) @@ -653,40 +967,46 @@ const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType:: if (cat_id.isNull() || !getCategory(cat_id)) { - cat_id = findCategoryUUIDForTypeInRoot(preferred_type, true, getRootFolderID()); + cat_id = findCategoryUUIDForTypeInRoot(preferred_type, getRootFolderID()); } return cat_id; } -const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type, bool create_folder) +const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const { - return findCategoryUUIDForTypeInRoot(preferred_type, create_folder, gInventory.getLibraryRootFolderID()); + return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getLibraryRootFolderID()); } // Convenience function to create a new category. You could call // updateCategory() with a newly generated UUID category, but this // version will take care of details like what the name should be -// based on preferred type. Returns the UUID of the new category. -LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, +// based on preferred type. +void LLInventoryModel::createNewCategory(const LLUUID& parent_id, LLFolderType::EType preferred_type, const std::string& pname, - inventory_func_type callback) + inventory_func_type callback, + const LLUUID& thumbnail_id) { LL_DEBUGS(LOG_INV) << "Create '" << pname << "' in '" << make_inventory_path(parent_id) << "'" << LL_ENDL; - LLUUID id; if (!isInventoryUsable()) { LL_WARNS(LOG_INV) << "Inventory is not usable; can't create requested category of type " << preferred_type << LL_ENDL; - // FIXME failing but still returning an id? - return id; + if (callback) + { + callback(LLUUID::null); + } + return; } if(LLFolderType::lookup(preferred_type) == LLFolderType::badLookup()) { LL_DEBUGS(LOG_INV) << "Attempt to create undefined category." << LL_ENDL; - // FIXME failing but still returning an id? - return id; + if (callback) + { + callback(LLUUID::null); + } + return; } if (preferred_type != LLFolderType::FT_NONE) @@ -697,26 +1017,72 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, LL_WARNS(LOG_INV) << "Creating new system folder, type " << preferred_type << LL_ENDL; } - id.generate(); std::string name = pname; - if(!pname.empty()) + if (pname.empty()) { - name.assign(pname); + name.assign(LLViewerFolderType::lookupNewCategoryName(preferred_type)); } - else + + if (AISAPI::isAvailable()) { - name.assign(LLViewerFolderType::lookupNewCategoryName(preferred_type)); + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["categories"] = LLSD::emptyArray(); + LLViewerInventoryCategory cat(LLUUID::null, parent_id, preferred_type, name, gAgent.getID()); + cat.setThumbnailUUID(thumbnail_id); + LLSD cat_sd = cat.asAISCreateCatLLSD(); + new_inventory["categories"].append(cat_sd); + AISAPI::CreateInventory( + parent_id, + new_inventory, + [this, callback, parent_id, preferred_type, name] (const LLUUID& new_category) + { + if (new_category.isNull()) + { + if (callback && !callback.empty()) + { + callback(new_category); + } + return; + } + + // todo: not needed since AIS does the accounting? + LLViewerInventoryCategory* folderp = gInventory.getCategory(new_category); + if (!folderp) + { + // Add the category to the internal representation + LLPointer<LLViewerInventoryCategory> cat = new LLViewerInventoryCategory( + new_category, + parent_id, + preferred_type, + name, + gAgent.getID()); + + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + accountForUpdate(update); + + cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 + cat->setDescendentCount(0); + updateCategory(cat); + } + + if (callback && !callback.empty()) + { + callback(new_category); + } + }); + return; } - + LLViewerRegion* viewer_region = gAgent.getRegion(); std::string url; if ( viewer_region ) url = viewer_region->getCapability("CreateInventoryCategory"); - if (!url.empty() && callback) + if (!url.empty()) { //Let's use the new capability. - + LLUUID id; + id.generate(); LLSD request, body; body["folder_id"] = id; body["parent_id"] = parent_id; @@ -729,44 +1095,13 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, LL_DEBUGS(LOG_INV) << "Creating category via request: " << ll_pretty_print_sd(request) << LL_ENDL; LLCoros::instance().launch("LLInventoryModel::createNewCategoryCoro", boost::bind(&LLInventoryModel::createNewCategoryCoro, this, url, body, callback)); - - return LLUUID::null; - } - - if (!gMessageSystem) - { - return LLUUID::null; + return; } - // FIXME this UDP code path needs to be removed. Requires - // reworking many of the callers to use callbacks rather than - // assuming instant success. - - // Add the category to the internal representation - LLPointer<LLViewerInventoryCategory> cat = - new LLViewerInventoryCategory(id, parent_id, preferred_type, name, gAgent.getID()); - cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 - cat->setDescendentCount(0); - LLCategoryUpdate update(cat->getParentUUID(), 1); - accountForUpdate(update); - updateCategory(cat); - - LL_DEBUGS(LOG_INV) << "Creating category via UDP message CreateInventoryFolder, type " << preferred_type << LL_ENDL; - - // Create the category on the server. We do this to prevent people - // from munging their protected folders. - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("CreateInventoryFolder"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlock("FolderData"); - cat->packMessage(msg); - gAgent.sendReliableMessage(); - - LL_INFOS(LOG_INV) << "Created new category '" << make_inventory_path(id) << "'" << LL_ENDL; - // return the folder id of the newly created folder - return id; + if (callback) + { + callback(LLUUID::null); // Notify about failure + } } void LLInventoryModel::createNewCategoryCoro(std::string url, LLSD postData, inventory_func_type callback) @@ -790,12 +1125,20 @@ void LLInventoryModel::createNewCategoryCoro(std::string url, LLSD postData, inv if (!status) { LL_WARNS() << "HTTP failure attempting to create category." << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } if (!result.has("folder_id")) { LL_WARNS() << "Malformed response contents" << ll_pretty_print_sd(result) << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -1300,7 +1643,7 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 mask |= LLInventoryObserver::LABEL; } // Under marketplace, category labels are quite complex and need extra upate - const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplace_id.notNull() && isObjectDescendentOf(cat->getUUID(), marketplace_id)) { mask |= LLInventoryObserver::LABEL; @@ -1442,17 +1785,25 @@ void LLInventoryModel::changeCategoryParent(LLViewerInventoryCategory* cat, notifyObservers(); } -void LLInventoryModel::onAISUpdateReceived(const std::string& context, const LLSD& update) +void LLInventoryModel::rebuildBrockenLinks() { - LLTimer timer; - if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) - { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); - } + // make sure we aren't adding expensive Rebuild to anything else. + notifyObservers(); - AISUpdate ais_update(update); // parse update llsd into stuff to do. - ais_update.doUpdate(); // execute the updates in the appropriate order. - LL_INFOS(LOG_INV) << "elapsed: " << timer.getElapsedTimeF32() << LL_ENDL; + for (const broken_links_t::value_type &link_list : mPossiblyBrockenLinks) + { + for (const LLUUID& link_id : link_list.second) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + } + for (const LLUUID& link_id : mLinksRebuildList) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + mPossiblyBrockenLinks.clear(); + mLinksRebuildList.clear(); + notifyObservers(); } // Does not appear to be used currently. @@ -1758,6 +2109,20 @@ void LLInventoryModel::idleNotifyObservers() { // *FIX: Think I want this conditional or moved elsewhere... handleResponses(true); + + if (mLinksRebuildList.size() > 0) + { + if (mModifyMask != LLInventoryObserver::NONE || (mChangedItemIDs.size() != 0)) + { + notifyObservers(); + } + for (const LLUUID& link_id : mLinksRebuildList) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + mLinksRebuildList.clear(); + notifyObservers(); + } if (mModifyMask == LLInventoryObserver::NONE && (mChangedItemIDs.size() == 0)) { @@ -2077,10 +2442,52 @@ void LLInventoryModel::addItem(LLViewerInventoryItem* item) // The item will show up as a broken link. if (item->getIsBrokenLink()) { - LL_INFOS(LOG_INV) << "Adding broken link [ name: " << item->getName() - << " itemID: " << item->getUUID() - << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + if (item->getAssetUUID().notNull() + && LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) + { + // Schedule this link for a recheck as inventory gets loaded + // Todo: expand to cover not just an initial fetch + mPossiblyBrockenLinks[item->getAssetUUID()].insert(item->getUUID()); + + // Do a blank rebuild of links once fetch is done + if (!mBulkFecthCallbackSlot.connected()) + { + // Links might take a while to update this way, and there + // might be a lot of them. A better option might be to check + // links periodically with final check on fetch completion. + mBulkFecthCallbackSlot = + LLInventoryModelBackgroundFetch::getInstance()->setFetchCompletionCallback( + [this]() + { + // rebuild is just in case, primary purpose is to wipe + // the list since we won't be getting anything 'new' + // see mLinksRebuildList + rebuildBrockenLinks(); + mBulkFecthCallbackSlot.disconnect(); + }); + } + LL_DEBUGS(LOG_INV) << "Scheduling a link to be rebuilt later [ name: " << item->getName() + << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + + } + else + { + LL_INFOS(LOG_INV) << "Adding broken link [ name: " << item->getName() + << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + } } + if (!mPossiblyBrockenLinks.empty()) + { + // check if we are waiting for this item + broken_links_t::iterator iter = mPossiblyBrockenLinks.find(item->getUUID()); + if (iter != mPossiblyBrockenLinks.end()) + { + mLinksRebuildList.insert(iter->second.begin() , iter->second.end()); + mPossiblyBrockenLinks.erase(iter); + } + } if (item->getIsLinkType()) { // Add back-link from linked-to UUID. @@ -2348,6 +2755,10 @@ bool LLInventoryModel::loadSkeleton( else { cached_ids.insert(tcat->getUUID()); + + // At the moment download does not provide a thumbnail + // uuid, use the one from cache + tcat->setThumbnailUUID(cat->getThumbnailUUID()); } } @@ -2635,7 +3046,7 @@ void LLInventoryModel::buildParentChildMap() } } - const BOOL COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, FALSE) != LLUUID::null); + const BOOL COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) != LLUUID::null); sFirstTimeInViewer2 = !COF_exists || gAgent.isFirstLogin(); @@ -2798,6 +3209,11 @@ void LLInventoryModel::initHttpRequest() mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_LLSD_XML); mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_INVENTORY); } + + if (!gGenericDispatcher.isHandlerPresent("BulkUpdateInventory")) + { + gGenericDispatcher.addHandler("BulkUpdateInventory", &sBulkUpdateInventory); + } } void LLInventoryModel::handleResponses(bool foreground) @@ -2850,14 +3266,17 @@ LLCore::HttpHandle LLInventoryModel::requestPost(bool foreground, void LLInventoryModel::createCommonSystemCategories() { - gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK, true); // folder should exist before user tries to 'landmark this' - gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS, true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, true); + //amount of System Folder we should wait for + sPendingSystemFolders = 8; + + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_TRASH); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_FAVORITE); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CALLINGCARD); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MY_OUTFITS); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_LANDMARK); // folder should exist before user tries to 'landmark this' + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_SETTINGS); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_INBOX); } struct LLUUIDAndName @@ -3082,9 +3501,6 @@ void LLInventoryModel::registerCallbacks(LLMessageSystem* msg) msg->setHandlerFuncFast(_PREHASH_RemoveInventoryItem, processRemoveInventoryItem, NULL); - msg->setHandlerFuncFast(_PREHASH_UpdateInventoryFolder, - processUpdateInventoryFolder, - NULL); msg->setHandlerFuncFast(_PREHASH_RemoveInventoryFolder, processRemoveInventoryFolder, NULL); @@ -3113,6 +3529,10 @@ void LLInventoryModel::processUpdateCreateInventoryItem(LLMessageSystem* msg, vo msg->getU32Fast(_PREHASH_InventoryData, _PREHASH_CallbackID, callback_id); gInventoryCallbacks.fire(callback_id, item_id); + + // todo: instead of unpacking message fully, + // grab only an item_id, then fetch + LLInventoryModelBackgroundFetch::instance().scheduleItemFetch(item_id, true); } } @@ -3228,66 +3648,6 @@ void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**) } // static -void LLInventoryModel::processUpdateInventoryFolder(LLMessageSystem* msg, - void**) -{ - LL_DEBUGS(LOG_INV) << "LLInventoryModel::processUpdateInventoryFolder()" << LL_ENDL; - LLUUID agent_id, folder_id, parent_id; - //char name[DB_INV_ITEM_NAME_BUF_SIZE]; - msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS(LOG_INV) << "Got an UpdateInventoryFolder for the wrong agent." - << LL_ENDL; - return; - } - LLPointer<LLViewerInventoryCategory> lastfolder; // hack - cat_array_t folders; - update_map_t update; - S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); - for(S32 i = 0; i < count; ++i) - { - LLPointer<LLViewerInventoryCategory> tfolder = new LLViewerInventoryCategory(gAgent.getID()); - lastfolder = tfolder; - tfolder->unpackMessage(msg, _PREHASH_FolderData, i); - // make sure it's not a protected folder - tfolder->setPreferredType(LLFolderType::FT_NONE); - folders.push_back(tfolder); - // examine update for changes. - LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); - if(folderp) - { - if(tfolder->getParentUUID() == folderp->getParentUUID()) - { - update[tfolder->getParentUUID()]; - } - else - { - ++update[tfolder->getParentUUID()]; - --update[folderp->getParentUUID()]; - } - } - else - { - ++update[tfolder->getParentUUID()]; - } - } - gInventory.accountForUpdate(update); - for (cat_array_t::iterator it = folders.begin(); it != folders.end(); ++it) - { - gInventory.updateCategory(*it); - } - gInventory.notifyObservers(); - - // *HACK: Do the 'show' logic for a new item in the inventory. - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - if (active_panel) - { - active_panel->setSelection(lastfolder->getUUID(), TAKE_FOCUS_NO); - } -} - -// static void LLInventoryModel::removeInventoryFolder(LLUUID agent_id, LLMessageSystem* msg) { @@ -3389,14 +3749,6 @@ void LLInventoryModel::processSaveAssetIntoInventory(LLMessageSystem* msg, } } -struct InventoryCallbackInfo -{ - InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) : - mCallback(callback), mInvID(inv_id) {} - U32 mCallback; - LLUUID mInvID; -}; - // static void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) { @@ -3442,15 +3794,22 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); if(folderp) { - if(tfolder->getParentUUID() == folderp->getParentUUID()) - { - update[tfolder->getParentUUID()]; - } - else - { - ++update[tfolder->getParentUUID()]; - --update[folderp->getParentUUID()]; - } + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + if (tfolder->getParentUUID() == folderp->getParentUUID()) + { + update[tfolder->getParentUUID()]; + } + else + { + ++update[tfolder->getParentUUID()]; + --update[folderp->getParentUUID()]; + } + } + else + { + folderp->fetch(); + } } else { @@ -3460,7 +3819,14 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) folderp = gInventory.getCategory(tfolder->getParentUUID()); if(folderp) { - ++update[tfolder->getParentUUID()]; + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + ++update[tfolder->getParentUUID()]; + } + else + { + folderp->fetch(); + } } } } @@ -3506,7 +3872,14 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); if(folderp) { - ++update[titem->getParentUUID()]; + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + ++update[titem->getParentUUID()]; + } + else + { + folderp->fetch(); + } } } } @@ -3520,10 +3893,20 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) for (cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) { gInventory.updateCategory(*cit); + + // Temporary workaround: just fetch the item using AIS to get missing fields. + // If this works fine we might want to extract ids only from the message + // then use AIS as a primary fetcher + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch((*cit)->getUUID(), true /*force, since it has changes*/); } for (item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) { gInventory.updateItem(*iit); + + // Temporary workaround: just fetch the item using AIS to get missing fields. + // If this works fine we might want to extract ids only from the message + // then use AIS as a primary fetcher + LLInventoryModelBackgroundFetch::instance().scheduleItemFetch((*iit)->getUUID(), true); } gInventory.notifyObservers(); @@ -4348,7 +4731,6 @@ LLPointer<LLInventoryValidationInfo> LLInventoryModel::validate() const } else if (count_under_root > 1) { - LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; validation_info->mDuplicateRequiredSystemFolders.insert(folder_type); if (!is_automatic && folder_type != LLFolderType::FT_SETTINGS) { @@ -4356,6 +4738,7 @@ LLPointer<LLInventoryValidationInfo> LLInventoryModel::validate() const // outfits, trash and other non-automatic folders. validation_info->mFatalSystemDuplicate++; fatal_errs++; + LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; } else { @@ -4364,6 +4747,7 @@ LLPointer<LLInventoryValidationInfo> LLInventoryModel::validate() const // Exception: FT_SETTINGS is not automatic, but only deserves a warning. validation_info->mWarnings["non_fatal_system_duplicate_under_root"]++; warning_count++; + LL_WARNS("Inventory") << "System folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; } } if (count_elsewhere > 0) diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 6c5706f871..4309c03f8e 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -226,10 +226,14 @@ private: //-------------------------------------------------------------------- public: static BOOL getIsFirstTimeInViewer2(); + static bool isSysFoldersReady() { return (sPendingSystemFolders == 0); } + private: static BOOL sFirstTimeInViewer2; const static S32 sCurrentInvCacheVersion; // expected inventory cache version + static S32 sPendingSystemFolders; + /** Initialization/Setup ** ** *******************************************************************************/ @@ -255,6 +259,7 @@ public: void getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t*& categories, item_array_t*& items) const; + void getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const; typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" // Compute a hash of direct descendant names (for detecting child name changes) @@ -302,24 +307,25 @@ public: // Find //-------------------------------------------------------------------- public: + + // Checks if category exists ("My Inventory" only), if it does not, creates it + void ensureCategoryForTypeExists(LLFolderType::EType preferred_type); + const LLUUID findCategoryUUIDForTypeInRoot( LLFolderType::EType preferred_type, - bool create_folder, - const LLUUID& root_id); + const LLUUID& root_id) const; // Returns the uuid of the category that specifies 'type' as what it // defaults to containing. The category is not necessarily only for that type. // NOTE: If create_folder is true, this will create a new inventory category // on the fly if one does not exist. *NOTE: if find_in_library is true it // will search in the user's library folder instead of "My Inventory" - const LLUUID findCategoryUUIDForType(LLFolderType::EType preferred_type, - bool create_folder = true); + const LLUUID findCategoryUUIDForType(LLFolderType::EType preferred_type) const; // will search in the user's library folder instead of "My Inventory" - const LLUUID findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type, - bool create_folder = true); + const LLUUID findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const; // Returns user specified category for uploads, returns default id if there are no // user specified one or it does not exist, creates default category if it is missing. - const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type); + const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const; // Get whatever special folder this object is a child of, if any. const LLViewerInventoryCategory *getFirstNondefaultParent(const LLUUID& obj_id) const; @@ -405,13 +411,15 @@ public: const LLUUID& new_parent_id, BOOL restamp); + // Marks links from a "possibly" broken list for a rebuild + // clears the list + void rebuildBrockenLinks(); + bool hasPosiblyBrockenLinks() const { return mPossiblyBrockenLinks.size() > 0; } + //-------------------------------------------------------------------- // Delete //-------------------------------------------------------------------- public: - - // Update model after an AISv3 update received for any operation. - void onAISUpdateReceived(const std::string& context, const LLSD& update); // Update model after an item is confirmed as removed from // server. Works for categories or items. @@ -475,10 +483,11 @@ public: public: // Returns the UUID of the new category. If you want to use the default // name based on type, pass in a NULL to the 'name' parameter. - LLUUID createNewCategory(const LLUUID& parent_id, + void createNewCategory(const LLUUID& parent_id, LLFolderType::EType preferred_type, const std::string& name, - inventory_func_type callback = NULL); + inventory_func_type callback = NULL, + const LLUUID& thumbnail_id = LLUUID::null); protected: // Internal methods that add inventory and make sure that all of // the internal data structures are consistent. These methods @@ -575,6 +584,10 @@ private: U32 mModifyMaskBacklog; changed_items_t mChangedItemIDsBacklog; changed_items_t mAddedItemIDsBacklog; + typedef std::map<LLUUID , changed_items_t> broken_links_t; + broken_links_t mPossiblyBrockenLinks; // there can be multiple links per item + changed_items_t mLinksRebuildList; + boost::signals2::connection mBulkFecthCallbackSlot; //-------------------------------------------------------------------- @@ -663,7 +676,6 @@ public: static void processUpdateCreateInventoryItem(LLMessageSystem* msg, void**); static void removeInventoryItem(LLUUID agent_id, LLMessageSystem* msg, const char* msg_label); static void processRemoveInventoryItem(LLMessageSystem* msg, void**); - static void processUpdateInventoryFolder(LLMessageSystem* msg, void**); static void removeInventoryFolder(LLUUID agent_id, LLMessageSystem* msg); static void processRemoveInventoryFolder(LLMessageSystem* msg, void**); static void processRemoveInventoryObjects(LLMessageSystem* msg, void**); diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index f544b318d6..91adef8047 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llinventorymodelbackgroundfetch.h" +#include "llaisapi.h" #include "llagent.h" #include "llappviewer.h" #include "llcallbacklist.h" @@ -186,12 +187,14 @@ const char * const LOG_INV("Inventory"); ///---------------------------------------------------------------------------- LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch(): - mBackgroundFetchActive(FALSE), + mBackgroundFetchActive(false), mFolderFetchActive(false), mFetchCount(0), - mAllFoldersFetched(FALSE), - mRecursiveInventoryFetchStarted(FALSE), - mRecursiveLibraryFetchStarted(FALSE), + mLastFetchCount(0), + mFetchFolderCount(0), + mAllRecursiveFoldersFetched(false), + mRecursiveInventoryFetchStarted(false), + mRecursiveLibraryFetchStarted(false), mMinTimeBetweenFetches(0.3f) {} @@ -200,7 +203,12 @@ LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch() bool LLInventoryModelBackgroundFetch::isBulkFetchProcessingComplete() const { - return mFetchQueue.empty() && mFetchCount <= 0; + return mFetchFolderQueue.empty() && mFetchItemQueue.empty() && mFetchCount <= 0; +} + +bool LLInventoryModelBackgroundFetch::isFolderFetchProcessingComplete() const +{ + return mFetchFolderQueue.empty() && mFetchFolderCount <= 0; } bool LLInventoryModelBackgroundFetch::libraryFetchStarted() const @@ -235,7 +243,7 @@ bool LLInventoryModelBackgroundFetch::inventoryFetchInProgress() const bool LLInventoryModelBackgroundFetch::isEverythingFetched() const { - return mAllFoldersFetched; + return mAllRecursiveFoldersFetched; } BOOL LLInventoryModelBackgroundFetch::folderFetchActive() const @@ -243,17 +251,33 @@ BOOL LLInventoryModelBackgroundFetch::folderFetchActive() const return mFolderFetchActive; } -void LLInventoryModelBackgroundFetch::addRequestAtFront(const LLUUID & id, BOOL recursive, bool is_category) +void LLInventoryModelBackgroundFetch::addRequestAtFront(const LLUUID & id, bool recursive, bool is_category) { - mFetchQueue.push_front(FetchQueueInfo(id, recursive, is_category)); + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (is_category) + { + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); + } + else + { + mFetchItemQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); + } } -void LLInventoryModelBackgroundFetch::addRequestAtBack(const LLUUID & id, BOOL recursive, bool is_category) +void LLInventoryModelBackgroundFetch::addRequestAtBack(const LLUUID & id, bool recursive, bool is_category) { - mFetchQueue.push_back(FetchQueueInfo(id, recursive, is_category)); + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (is_category) + { + mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); + } + else + { + mFetchItemQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); + } } -void LLInventoryModelBackgroundFetch::start(const LLUUID& id, BOOL recursive) +void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) { LLViewerInventoryCategory * cat(gInventory.getCategory(id)); @@ -262,31 +286,53 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, BOOL recursive) // it's a folder, do a bulk fetch LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL; - mBackgroundFetchActive = TRUE; + mBackgroundFetchActive = true; mFolderFetchActive = true; + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; if (id.isNull()) { if (! mRecursiveInventoryFetchStarted) { mRecursiveInventoryFetchStarted |= recursive; - mFetchQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursive)); + if (recursive && AISAPI::isAvailable()) + { + // Not only root folder can be massive, but + // most system folders will be requested independently + // so request root folder and content separately + mFetchFolderQueue.push_front(FetchQueueInfo(gInventory.getRootFolderID(), FT_FOLDER_AND_CONTENT)); + } + else + { + mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursion_type)); + } gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); } if (! mRecursiveLibraryFetchStarted) { mRecursiveLibraryFetchStarted |= recursive; - mFetchQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursive)); + mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursion_type)); gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); } } else { - // Specific folder requests go to front of queue. - if (mFetchQueue.empty() || mFetchQueue.front().mUUID != id) - { - mFetchQueue.push_front(FetchQueueInfo(id, recursive)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } + if (AISAPI::isAvailable()) + { + if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != id) + { + // On AIS make sure root goes to the top and follow up recursive + // fetches, not individual requests + mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + } + else if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != id) + { + // Specific folder requests go to front of queue. + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + if (id == gInventory.getLibraryRootFolderID()) { mRecursiveLibraryFetchStarted |= recursive; @@ -299,21 +345,41 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, BOOL recursive) } else if (LLViewerInventoryItem * itemp = gInventory.getItem(id)) { - if (! itemp->mIsComplete && (mFetchQueue.empty() || mFetchQueue.front().mUUID != id)) + if (! itemp->mIsComplete) { - mBackgroundFetchActive = TRUE; - - mFetchQueue.push_front(FetchQueueInfo(id, false, false)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + scheduleItemFetch(id); } } } +void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, bool forced) +{ + if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) + { + mBackgroundFetchActive = true; + + // Specific folder requests go to front of queue. + mFetchFolderQueue.push_front(FetchQueueInfo(cat_id, forced ? FT_FORCED : FT_DEFAULT)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} + +void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, bool forced) +{ + if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id) + { + mBackgroundFetchActive = true; + + mFetchItemQueue.push_front(FetchQueueInfo(item_id, forced ? FT_FORCED : FT_DEFAULT, false)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} + void LLInventoryModelBackgroundFetch::findLostItems() { - mBackgroundFetchActive = TRUE; - mFolderFetchActive = true; - mFetchQueue.push_back(FetchQueueInfo(LLUUID::null, TRUE)); + mBackgroundFetchActive = true; + mFolderFetchActive = true; + mFetchFolderQueue.push_back(FetchQueueInfo(LLUUID::null, FT_RECURSIVE)); gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); } @@ -322,15 +388,28 @@ void LLInventoryModelBackgroundFetch::setAllFoldersFetched() if (mRecursiveInventoryFetchStarted && mRecursiveLibraryFetchStarted) { - mAllFoldersFetched = TRUE; + mAllRecursiveFoldersFetched = true; //LL_INFOS(LOG_INV) << "All folders fetched, validating" << LL_ENDL; //gInventory.validate(); } + mFolderFetchActive = false; - mBackgroundFetchActive = false; + if (isBulkFetchProcessingComplete()) + { + mBackgroundFetchActive = false; + } + + // For now only informs about initial fetch being done + mFoldersFetchedSignal(); + LL_INFOS(LOG_INV) << "Inventory background fetch completed" << LL_ENDL; } +boost::signals2::connection LLInventoryModelBackgroundFetch::setFetchCompletionCallback(folders_fetched_callback_t cb) +{ + return mFoldersFetchedSignal.connect(cb); +} + void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *) { LLInventoryModelBackgroundFetch::instance().backgroundFetch(); @@ -338,10 +417,17 @@ void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *) void LLInventoryModelBackgroundFetch::backgroundFetch() { - if (mBackgroundFetchActive && gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) + if (mBackgroundFetchActive) { - // If we'll be using the capability, we'll be sending batches and the background thing isn't as important. - bulkFetch(); + if (AISAPI::isAvailable()) + { + bulkFetchViaAis(); + } + else if (gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) + { + // If we'll be using the capability, we'll be sending batches and the background thing isn't as important. + bulkFetch(); + } } } @@ -354,9 +440,476 @@ void LLInventoryModelBackgroundFetch::incrFetchCount(S32 fetching) mFetchCount = 0; } } +void LLInventoryModelBackgroundFetch::incrFetchFolderCount(S32 fetching) +{ + incrFetchCount(fetching); + mFetchFolderCount += fetching; + if (mFetchCount < 0) + { + LL_WARNS_ONCE(LOG_INV) << "Inventory fetch count fell below zero (0)." << LL_ENDL; + mFetchFolderCount = 0; + } +} + +void ais_simple_item_callback(const LLUUID& inv_id) +{ + LL_DEBUGS(LOG_INV , "AIS3") << "Response for " << inv_id << LL_ENDL; + LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); +} + +void LLInventoryModelBackgroundFetch::onAISContentCalback( + const LLUUID& request_id, + const uuid_vec_t& content_ids, + const LLUUID& response_id, + EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + + uuid_vec_t::const_iterator folder_iter = content_ids.begin(); + uuid_vec_t::const_iterator folder_end = content_ids.end(); + while (folder_iter != folder_end) + { + std::list<LLUUID>::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), *folder_iter); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + + LLViewerInventoryCategory* cat(gInventory.getCategory(*folder_iter)); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + if (response_id.isNull()) + { + // Failed to fetch, get it individually + mFetchFolderQueue.push_back(FetchQueueInfo(*folder_iter, FT_RECURSIVE)); + } + else + { + // push descendant back to verify they are fetched fully (ex: didn't encounter depth limit) + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(*folder_iter, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + folder_iter++; + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} +void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + std::list<LLUUID>::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), request_id); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + else + { + // ais shouldn't respond twice + llassert(false); + LL_WARNS() << "Unexpected folder response for " << request_id << LL_ENDL; + } + + if (request_id.isNull()) + { + // orhans, no other actions needed + return; + } + + bool request_descendants = false; + if (response_id.isNull()) // Failure + { + LL_DEBUGS(LOG_INV , "AIS3") << "Failure response for folder " << request_id << LL_ENDL; + if (fetch_type == FT_RECURSIVE) + { + // A full recursive request failed. + // Try requesting folder and nested content separately + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_FOLDER_AND_CONTENT)); + } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + LL_WARNS() << "Failed to download folder: " << request_id << " Requesting known content separately" << LL_ENDL; + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + + // set folder's version to prevent viewer from trying to request folder indefinetely + LLViewerInventoryCategory* cat(gInventory.getCategory(request_id)); + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + cat->setVersion(0); + } + } + } + else + { + if (fetch_type == FT_RECURSIVE) + { + // Got the folder and content, now verify content + // Request content even for FT_RECURSIVE in case of changes, failures + // or if depth limit gets imlemented. + // This shouldn't redownload folders if they already have version + request_descendants = true; + LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << ". Requesting content" << LL_ENDL; + } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + // readd folder for content request + mFetchFolderQueue.push_front(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + } + else + { + LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << "." << LL_ENDL; + } + + } + + if (request_descendants) + { + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(request_id, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + + // done + LLViewerInventoryCategory * cat(gInventory.getCategory(request_id)); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } +} static LLTrace::BlockTimerStatHandle FTM_BULK_FETCH("Bulk Fetch"); +void LLInventoryModelBackgroundFetch::bulkFetchViaAis() +{ + LL_RECORD_BLOCK_TIME(FTM_BULK_FETCH); + //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. + if (gDisconnected) + { + return; + } + + static LLCachedControl<U32> ais_pool(gSavedSettings, "PoolSizeAIS", 20); + // Don't have too many requests at once, AIS throttles + // Reserve one request for actions outside of fetch (like renames) + const U32 max_concurrent_fetches = llclamp(ais_pool - 1, 1, 50); + + if (mFetchCount >= max_concurrent_fetches) + { + return; + } + + // Don't loop for too long (in case of large, fully loaded inventory) + F64 curent_time = LLTimer::getTotalSeconds(); + const F64 max_time = LLStartUp::getStartupState() > STATE_WEARABLES_WAIT + ? 0.006f // 6 ms + : 1.f; + const F64 end_time = curent_time + max_time; + S32 last_fetch_count = mFetchCount; + + while (!mFetchFolderQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) + { + const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); + bulkFetchViaAis(fetch_info); + mFetchFolderQueue.pop_front(); + curent_time = LLTimer::getTotalSeconds(); + } + + // Ideally we shouldn't fetch items if recursive fetch isn't done, + // but there is a chance some request will start timeouting and recursive + // fetch will get stuck on a signle folder, don't block item fetch in such case + while (!mFetchItemQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) + { + const FetchQueueInfo& fetch_info(mFetchItemQueue.front()); + bulkFetchViaAis(fetch_info); + mFetchItemQueue.pop_front(); + curent_time = LLTimer::getTotalSeconds(); + } + + if (last_fetch_count != mFetchCount // if anything was added + || mLastFetchCount != mFetchCount) // if anything was substracted + { + LL_DEBUGS(LOG_INV , "AIS3") << "Total active fetches: " << mLastFetchCount << "->" << last_fetch_count << "->" << mFetchCount + << ", scheduled folder fetches: " << (S32)mFetchFolderQueue.size() + << ", scheduled item fetches: " << (S32)mFetchItemQueue.size() + << LL_ENDL; + mLastFetchCount = mFetchCount; + + if (!mExpectedFolderIds.empty()) + { + // A folder seem to be stack fetching on QA account, print oldest folder out + LL_DEBUGS(LOG_INV , "AIS3") << "Oldest expected folder: "; + std::list<LLUUID>::const_iterator iter = mExpectedFolderIds.begin(); + LL_CONT << *iter; + if ((*iter).notNull()) + { + LLViewerInventoryCategory* cat(gInventory.getCategory(*iter)); + if (cat) + { + LL_CONT << " Folder name: " << cat->getName() << " Parent: " << cat->getParentUUID(); + } + else + { + LL_CONT << " This folder doesn't exist"; + } + } + else + { + LL_CONT << " Orphans request"; + } + LL_CONT << LL_ENDL; + } + } + + if (isFolderFetchProcessingComplete() && mFolderFetchActive) + { + setAllFoldersFetched(); + } + + if (isBulkFetchProcessingComplete()) + { + mBackgroundFetchActive = false; + } +} + +void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetch_info) +{ + if (fetch_info.mIsCategory) + { + const LLUUID & cat_id(fetch_info.mUUID); + if (cat_id.isNull()) + { + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + // Lost and found + // Should it actually be recursive? + AISAPI::FetchOrphans([](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(LLUUID::null, + response_id, + FT_DEFAULT); + }); + } + else + { + LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); + if (cat) + { + if (fetch_info.mFetchType == FT_CONTENT_RECURSIVE) + { + // fetch content only, ignore cat itself + uuid_vec_t children; + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + + LLViewerInventoryCategory::EFetchType target_state = LLViewerInventoryCategory::FETCH_RECURSIVE; + bool content_done = true; + + // Top limit is 'as many as you can put into url' + static LLCachedControl<S32> ais_batch(gSavedSettings, "BatchSizeAIS3", 20); + S32 batch_limit = llclamp(ais_batch(), 1, 40); + + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion() + || child_cat->getFetching() >= target_state) + { + continue; + } + + if (child_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + // special case + content_done = false; + if (children.empty()) + { + // fetch marketplace alone + // Should it actually be fetched as FT_FOLDER_AND_CONTENT? + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + break; + } + else + { + // fetch marketplace alone next run + continue; + } + } + + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + + if (children.size() >= batch_limit) + { + content_done = false; + break; + } + } + + if (!children.empty()) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_id = cat->getUUID(); // need a copy for lambda + AISAPI::completion_t cb = [cat_id, children, type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISContentCalback(cat_id, children, response_id, type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategorySubset(cat_id, children, item_type, true, cb, 0); + } + + if (content_done) + { + // This will have a bit of overlap with onAISContentCalback, + // but something else might have dowloaded folders, so verify + // every child that is complete has it's children done as well + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion()) + { + mFetchFolderQueue.push_back(FetchQueueInfo(child_cat->getUUID(), FT_RECURSIVE)); + } + } + } + else + { + // send it back to get the rest + mFetchFolderQueue.push_back(FetchQueueInfo(cat_id, FT_CONTENT_RECURSIVE)); + } + } + else if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() + || fetch_info.mFetchType == FT_FORCED) + { + LLViewerInventoryCategory::EFetchType target_state = + fetch_info.mFetchType > FT_CONTENT_RECURSIVE + ? LLViewerInventoryCategory::FETCH_RECURSIVE + : LLViewerInventoryCategory::FETCH_NORMAL; + // start again if we did a non-recursive fetch before + // to get all children in a single request + if (cat->getFetching() < target_state) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + cat->setFetching(target_state); + mExpectedFolderIds.push_back(cat_id); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_id = cat->getUUID(); + AISAPI::completion_t cb = [cat_id , type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(cat_id , response_id , type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategoryChildren(cat_id , item_type , type == FT_RECURSIVE , cb, 0); + } + } + else + { + // Already fetched, check if anything inside needs fetching + if (fetch_info.mFetchType == FT_RECURSIVE + || fetch_info.mFetchType == FT_FOLDER_AND_CONTENT) + { + LLInventoryModel::cat_array_t * categories(NULL); + LLInventoryModel::item_array_t * items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + // not push_front to not cause an infinite loop + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + } // else try to fetch folder either way? + } + } + else + { + LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); + + if (itemp) + { + if (!itemp->isFinished() || fetch_info.mFetchType == FT_FORCED) + { + mFetchCount++; + if (itemp->getPermissions().getOwner() == gAgent.getID()) + { + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); + } + else + { + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::LIBRARY, ais_simple_item_callback); + } + } + } + else // We don't know it, assume incomplete + { + // Assume agent's inventory, library wouldn't have gotten here + mFetchCount++; + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); + } + } +} + // Bundle up a bunch of requests to send all at once. void LLInventoryModelBackgroundFetch::bulkFetch() { @@ -376,13 +929,6 @@ void LLInventoryModelBackgroundFetch::bulkFetch() // inventory more quickly. static const U32 max_batch_size(10); static const S32 max_concurrent_fetches(12); // Outstanding requests, not connections - static const F32 new_min_time(0.05f); // *HACK: Clean this up when old code goes away entirely. - - mMinTimeBetweenFetches = new_min_time; - if (mMinTimeBetweenFetches < new_min_time) - { - mMinTimeBetweenFetches = new_min_time; // *HACK: See above. - } if (mFetchCount) { @@ -396,8 +942,7 @@ void LLInventoryModelBackgroundFetch::bulkFetch() gInventory.notifyObservers(); } - if ((mFetchCount > max_concurrent_fetches) || - (mFetchTimer.getElapsedTimeF32() < mMinTimeBetweenFetches)) + if (mFetchCount > max_concurrent_fetches) { return; } @@ -417,95 +962,105 @@ void LLInventoryModelBackgroundFetch::bulkFetch() LLSD item_request_body; LLSD item_request_body_lib; - while (! mFetchQueue.empty() + while (! mFetchFolderQueue.empty() && (item_count + folder_count) < max_batch_size) { - const FetchQueueInfo & fetch_info(mFetchQueue.front()); + const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); if (fetch_info.mIsCategory) { const LLUUID & cat_id(fetch_info.mUUID); - if (cat_id.isNull()) //DEV-17797 + if (cat_id.isNull()) //DEV-17797 Lost and found { LLSD folder_sd; folder_sd["folder_id"] = LLUUID::null.asString(); folder_sd["owner_id"] = gAgent.getID(); folder_sd["sort_order"] = LLSD::Integer(sort_order); - folder_sd["fetch_folders"] = LLSD::Boolean(FALSE); - folder_sd["fetch_items"] = LLSD::Boolean(TRUE); + folder_sd["fetch_folders"] = LLSD::Boolean(false); + folder_sd["fetch_items"] = LLSD::Boolean(true); folder_request_body["folders"].append(folder_sd); folder_count++; } else { - const LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); - - if (cat) - { - if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() - && std::find(all_cats.begin(), all_cats.end(), cat_id) == all_cats.end()) - { - LLSD folder_sd; - folder_sd["folder_id"] = cat->getUUID(); - folder_sd["owner_id"] = cat->getOwnerID(); - folder_sd["sort_order"] = LLSD::Integer(sort_order); - folder_sd["fetch_folders"] = LLSD::Boolean(TRUE); //(LLSD::Boolean)sFullFetchStarted; - folder_sd["fetch_items"] = LLSD::Boolean(TRUE); - - if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) - { - folder_request_body_lib["folders"].append(folder_sd); - } - else - { - folder_request_body["folders"].append(folder_sd); - } - folder_count++; - } - - // May already have this folder, but append child folders to list. - if (fetch_info.mRecursive) - { - LLInventoryModel::cat_array_t * categories(NULL); - LLInventoryModel::item_array_t * items(NULL); - gInventory.getDirectDescendentsOf(cat->getUUID(), categories, items); - for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); - it != categories->end(); - ++it) - { - mFetchQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mRecursive)); - } - } - } + const LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); + if (cat) + { + if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion()) + { + if (std::find(all_cats.begin(), all_cats.end(), cat_id) == all_cats.end()) + { + LLSD folder_sd; + folder_sd["folder_id"] = cat->getUUID(); + folder_sd["owner_id"] = cat->getOwnerID(); + folder_sd["sort_order"] = LLSD::Integer(sort_order); + folder_sd["fetch_folders"] = LLSD::Boolean(TRUE); //(LLSD::Boolean)sFullFetchStarted; + folder_sd["fetch_items"] = LLSD::Boolean(TRUE); + + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + folder_request_body_lib["folders"].append(folder_sd); + } + else + { + folder_request_body["folders"].append(folder_sd); + } + folder_count++; + } + } + else + { + // May already have this folder, but append child folders to list. + if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) + { + LLInventoryModel::cat_array_t * categories(NULL); + LLInventoryModel::item_array_t * items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mFetchType)); + } + } + } + } } - if (fetch_info.mRecursive) + if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) { recursive_cats.push_back(cat_id); } all_cats.push_back(cat_id); } - else - { - LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); - if (itemp) - { - LLSD item_sd; - item_sd["owner_id"] = itemp->getPermissions().getOwner(); - item_sd["item_id"] = itemp->getUUID(); - if (itemp->getPermissions().getOwner() == gAgent.getID()) - { - item_request_body.append(item_sd); - } - else - { - item_request_body_lib.append(item_sd); - } - //itemp->fetchFromServer(); - item_count++; - } - } + mFetchFolderQueue.pop_front(); + } + + + while (!mFetchItemQueue.empty() + && (item_count + folder_count) < max_batch_size) + { + const FetchQueueInfo & fetch_info(mFetchItemQueue.front()); + + LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); + + if (itemp) + { + LLSD item_sd; + item_sd["owner_id"] = itemp->getPermissions().getOwner(); + item_sd["item_id"] = itemp->getUUID(); + if (itemp->getPermissions().getOwner() == gAgent.getID()) + { + item_request_body.append(item_sd); + } + else + { + item_request_body_lib.append(item_sd); + } + //itemp->fetchFromServer(); + item_count++; + } - mFetchQueue.pop_front(); + mFetchItemQueue.pop_front(); } // Issue HTTP POST requests to fetch folders and items @@ -576,14 +1131,22 @@ void LLInventoryModelBackgroundFetch::bulkFetch() bool LLInventoryModelBackgroundFetch::fetchQueueContainsNoDescendentsOf(const LLUUID & cat_id) const { - for (fetch_queue_t::const_iterator it = mFetchQueue.begin(); - it != mFetchQueue.end(); + for (fetch_queue_t::const_iterator it = mFetchFolderQueue.begin(); + it != mFetchFolderQueue.end(); ++it) { const LLUUID & fetch_id = (*it).mUUID; if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) return false; } + for (fetch_queue_t::const_iterator it = mFetchItemQueue.begin(); + it != mFetchItemQueue.end(); + ++it) + { + const LLUUID & fetch_id = (*it).mUUID; + if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) + return false; + } return true; } @@ -829,10 +1392,10 @@ void BGFolderHttpHandler::processFailure(LLCore::HttpStatus status, LLCore::Http while (iter != end) { folders.append(*iter); - LLUUID fodler_id = iter->get("folder_id").asUUID(); - if (std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), fodler_id) != mRecursiveCatUUIDs.end()) + LLUUID folder_id = iter->get("folder_id").asUUID(); + if (std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), folder_id) != mRecursiveCatUUIDs.end()) { - recursive_cats.push_back(fodler_id); + recursive_cats.push_back(folder_id); } if (folders.size() == (S32)(size / 2)) { diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h index 00d2908c1b..e7be265a3d 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.h +++ b/indra/newview/llinventorymodelbackgroundfetch.h @@ -47,9 +47,11 @@ class LLInventoryModelBackgroundFetch : public LLSingleton<LLInventoryModelBackg ~LLInventoryModelBackgroundFetch(); public: - // Start and stop background breadth-first fetching of inventory contents. + // Start background breadth-first fetching of inventory contents. // This gets triggered when performing a filter-search. - void start(const LLUUID& cat_id = LLUUID::null, BOOL recursive = TRUE); + void start(const LLUUID& cat_id = LLUUID::null, bool recursive = true); + void scheduleFolderFetch(const LLUUID& cat_id, bool forced = false); + void scheduleItemFetch(const LLUUID& item_id, bool forced = false); BOOL folderFetchActive() const; bool isEverythingFetched() const; // completing the fetch once per session should be sufficient @@ -62,16 +64,47 @@ public: bool inventoryFetchCompleted() const; bool inventoryFetchInProgress() const; - void findLostItems(); - void incrFetchCount(S32 fetching); + void findLostItems(); + void incrFetchCount(S32 fetching); + void incrFetchFolderCount(S32 fetching); bool isBulkFetchProcessingComplete() const; + bool isFolderFetchProcessingComplete() const; void setAllFoldersFetched(); - void addRequestAtFront(const LLUUID & id, BOOL recursive, bool is_category); - void addRequestAtBack(const LLUUID & id, BOOL recursive, bool is_category); + typedef boost::function<void()> folders_fetched_callback_t; + boost::signals2::connection setFetchCompletionCallback(folders_fetched_callback_t cb); + + void addRequestAtFront(const LLUUID & id, bool recursive, bool is_category); + void addRequestAtBack(const LLUUID & id, bool recursive, bool is_category); protected: + + typedef enum { + FT_DEFAULT = 0, + FT_FORCED, // request non-recursively even if already loaded + FT_CONTENT_RECURSIVE, // request content recursively + FT_FOLDER_AND_CONTENT, // request folder, then content recursively + FT_RECURSIVE, // request everything recursively + } EFetchType; + struct FetchQueueInfo + { + FetchQueueInfo(const LLUUID& id, EFetchType recursive, bool is_category = true) + : mUUID(id), + mIsCategory(is_category), + mFetchType(recursive) + {} + + LLUUID mUUID; + bool mIsCategory; + EFetchType mFetchType; + }; + typedef std::deque<FetchQueueInfo> fetch_queue_t; + + void onAISContentCalback(const LLUUID& request_id, const uuid_vec_t &content_ids, const LLUUID& response_id, EFetchType fetch_type); + void onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type); + void bulkFetchViaAis(); + void bulkFetchViaAis(const FetchQueueInfo& fetch_info); void bulkFetch(); void backgroundFetch(); @@ -80,31 +113,23 @@ protected: bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const; private: - BOOL mRecursiveInventoryFetchStarted; - BOOL mRecursiveLibraryFetchStarted; - BOOL mAllFoldersFetched; + bool mRecursiveInventoryFetchStarted; + bool mRecursiveLibraryFetchStarted; + bool mAllRecursiveFoldersFetched; + typedef boost::signals2::signal<void()> folders_fetched_signal_t; + folders_fetched_signal_t mFoldersFetchedSignal; - BOOL mBackgroundFetchActive; + bool mBackgroundFetchActive; bool mFolderFetchActive; S32 mFetchCount; + S32 mLastFetchCount; // for debug + S32 mFetchFolderCount; LLFrameTimer mFetchTimer; F32 mMinTimeBetweenFetches; - - struct FetchQueueInfo - { - FetchQueueInfo(const LLUUID& id, BOOL recursive, bool is_category = true) - : mUUID(id), - mIsCategory(is_category), - mRecursive(recursive) - {} - - LLUUID mUUID; - bool mIsCategory; - BOOL mRecursive; - }; - typedef std::deque<FetchQueueInfo> fetch_queue_t; - fetch_queue_t mFetchQueue; + fetch_queue_t mFetchFolderQueue; + fetch_queue_t mFetchItemQueue; + std::list<LLUUID> mExpectedFolderIds; // for debug, should this track time? }; #endif // LL_LLINVENTORYMODELBACKGROUNDFETCH_H diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index db0751cb89..281a8bc789 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -37,8 +37,10 @@ #include "llagent.h" #include "llagentwearables.h" +#include "llaisapi.h" #include "llfloater.h" #include "llfocusmgr.h" +#include "llinventorymodelbackgroundfetch.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" @@ -56,6 +58,7 @@ #include "llsdutil.h" #include <deque> +const S32 LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS = 7; const F32 LLInventoryFetchItemsObserver::FETCH_TIMER_EXPIRY = 60.0f; @@ -149,7 +152,7 @@ LLInventoryFetchItemsObserver::LLInventoryFetchItemsObserver(const uuid_vec_t& i void LLInventoryFetchItemsObserver::changed(U32 mask) { - LL_DEBUGS() << this << " remaining incomplete " << mIncomplete.size() + LL_DEBUGS("InventoryFetch") << this << " remaining incomplete " << mIncomplete.size() << " complete " << mComplete.size() << " wait period " << mFetchingPeriod.getRemainingTimeF32() << LL_ENDL; @@ -158,6 +161,15 @@ void LLInventoryFetchItemsObserver::changed(U32 mask) // appropriate. if (!mIncomplete.empty()) { + if (!LLInventoryModelBackgroundFetch::getInstance()->isEverythingFetched()) + { + // Folders have a priority over items and they download items as well + // Wait untill initial folder fetch is done + LL_DEBUGS("InventoryFetch") << "Folder fetch in progress, resetting fetch timer" << LL_ENDL; + + mFetchingPeriod.reset(); + mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } // Have we exceeded max wait time? bool timeout_expired = mFetchingPeriod.hasExpired(); @@ -176,7 +188,7 @@ void LLInventoryFetchItemsObserver::changed(U32 mask) if (timeout_expired) { // Just concede that this item hasn't arrived in reasonable time and continue on. - LL_WARNS() << "Fetcher timed out when fetching inventory item UUID: " << item_id << LL_ENDL; + LL_WARNS("InventoryFetch") << "Fetcher timed out when fetching inventory item UUID: " << item_id << LL_ENDL; it = mIncomplete.erase(it); } else @@ -191,7 +203,7 @@ void LLInventoryFetchItemsObserver::changed(U32 mask) if (mIncomplete.empty()) { - LL_DEBUGS() << this << " done at remaining incomplete " + LL_DEBUGS("InventoryFetch") << this << " done at remaining incomplete " << mIncomplete.size() << " complete " << mComplete.size() << LL_ENDL; done(); } @@ -251,29 +263,21 @@ void fetch_items_from_llsd(const LLSD& items_llsd) void LLInventoryFetchItemsObserver::startFetch() { - LLUUID owner_id; + bool aisv3 = AISAPI::isAvailable(); + LLSD items_llsd; + + typedef std::map<LLUUID, uuid_vec_t> requests_by_folders_t; + requests_by_folders_t requests; for (uuid_vec_t::const_iterator it = mIDs.begin(); it < mIDs.end(); ++it) { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if (item) - { - if (item->isFinished()) - { - // It's complete, so put it on the complete container. - mComplete.push_back(*it); - continue; - } - else - { - owner_id = item->getPermissions().getOwner(); - } - } - else - { - // assume it's agent inventory. - owner_id = gAgent.getID(); - } + LLViewerInventoryItem* item = gInventory.getItem(*it); + if (item && item->isFinished()) + { + // It's complete, so put it on the complete container. + mComplete.push_back(*it); + continue; + } // Ignore categories since they're not items. We // could also just add this to mComplete but not sure what the @@ -294,17 +298,98 @@ void LLInventoryFetchItemsObserver::startFetch() // pack this on the message. mIncomplete.push_back(*it); - // Prepare the data to fetch - LLSD item_entry; - item_entry["owner_id"] = owner_id; - item_entry["item_id"] = (*it); - items_llsd.append(item_entry); + if (aisv3) + { + if (item) + { + LLUUID parent_id = item->getParentUUID(); + requests[parent_id].push_back(*it); + } + else + { + // Can happen for gestures and calling cards if server notified us before they fetched + // Request by id without checking for an item. + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(*it); + } + } + else + { + // Prepare the data to fetch + LLSD item_entry; + if (item) + { + item_entry["owner_id"] = item->getPermissions().getOwner(); + } + else + { + // assume it's agent inventory. + item_entry["owner_id"] = gAgent.getID(); + } + item_entry["item_id"] = (*it); + items_llsd.append(item_entry); + } } mFetchingPeriod.reset(); mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); - fetch_items_from_llsd(items_llsd); + if (aisv3) + { + for (requests_by_folders_t::value_type &folder : requests) + { + if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS) + { + // requesting one by one will take a while + // do whole folder + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + } + else + { + LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first); + if (cat) + { + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // start fetching whole folder since it's not ready either way + cat->fetch(); + } + else if (cat->getViewerDescendentCount() <= folder.second.size() + || cat->getDescendentCount() <= folder.second.size()) + { + // Start fetching whole folder since we need all items + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + + } + else + { + // get items one by one + for (LLUUID &item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } + } + else + { + // Isn't supposed to happen? We should have all folders + // and if item exists, folder is supposed to exist as well. + llassert(false); + LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL; + + // get items one by one + for (LLUUID &item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } + } + } + } + else + { + fetch_items_from_llsd(items_llsd); + } + } LLInventoryFetchDescendentsObserver::LLInventoryFetchDescendentsObserver(const LLUUID& cat_id) : @@ -649,6 +734,13 @@ void LLInventoryCategoriesObserver::changed(U32 mask) } } + const LLUUID thumbnail_id = category->getThumbnailUUID(); + if (cat_data.mThumbnailId != thumbnail_id) + { + cat_data.mThumbnailId = thumbnail_id; + cat_changed = true; + } + // If anything has changed above, fire the callback. if (cat_changed) cat_data.mCallback(); @@ -666,6 +758,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; S32 current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN; bool can_be_added = true; + LLUUID thumbnail_id; LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); // If category could not be retrieved it might mean that @@ -677,6 +770,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t // Inventory category version is used to find out if some changes // to a category have been made. version = category->getVersion(); + thumbnail_id = category->getThumbnailUUID(); LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; @@ -701,12 +795,12 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t { if(init_name_hash) { - digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, cb, version, current_num_known_descendents,item_name_hash))); + digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents,item_name_hash))); } else { - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, cb, version, current_num_known_descendents))); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents))); } } @@ -719,23 +813,25 @@ void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents) + const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) + , mThumbnailId(thumbnail_id) , mIsNameHashInitialized(false) { } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) + const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) + , mThumbnailId(thumbnail_id) , mIsNameHashInitialized(true) , mItemNameHash(name_hash) { diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index 4af8102055..bec08d2cdf 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -104,6 +104,9 @@ public: /*virtual*/ void startFetch(); /*virtual*/ void changed(U32 mask); + + // For attempts to group requests if too many items are requested + static const S32 MAX_INDIVIDUAL_ITEM_REQUESTS; private: LLTimer mFetchingPeriod; @@ -125,7 +128,7 @@ public: LLInventoryFetchDescendentsObserver(const LLUUID& cat_id = LLUUID::null); LLInventoryFetchDescendentsObserver(const uuid_vec_t& cat_ids); - /*virtual*/ void startFetch(); + virtual void startFetch(); /*virtual*/ void changed(U32 mask); protected: BOOL isCategoryComplete(const LLViewerInventoryCategory* cat) const; @@ -273,14 +276,15 @@ protected: typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" struct LLCategoryData { - LLCategoryData(const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents); - LLCategoryData(const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); + LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents); + LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); callback_t mCallback; S32 mVersion; S32 mDescendentsCount; digest_t mItemNameHash; bool mIsNameHashInitialized; LLUUID mCatID; + LLUUID mThumbnailId; }; typedef std::map<LLUUID, LLCategoryData> category_map_t; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 2799cb4cdf..54f91451ac 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -40,6 +40,7 @@ #include "llfolderviewitem.h" #include "llfloaterimcontainer.h" #include "llimview.h" +#include "llinspecttexture.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llinventorymodelbackgroundfetch.h" @@ -160,13 +161,15 @@ LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : mInvFVBridgeBuilder(NULL), mInventoryViewModel(p.name), mGroupedItemBridge(new LLFolderViewGroupedItemBridge), - mFocusSelection(false) + mFocusSelection(false), + mBuildChildrenViews(true), + mRootInited(false) { mInvFVBridgeBuilder = &INVENTORY_BRIDGE_BUILDER; if (!sColorSetInitialized) { - sDefaultColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + sDefaultColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE); sDefaultHighlightColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE); sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); @@ -182,6 +185,7 @@ LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&LLInventoryPanel::beginIMSession, this)); mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryPanel::fileUploadLocation, this, _2)); + mCommitCallbackRegistrar.add("Inventory.OpenNewFolderWindow", boost::bind(&LLInventoryPanel::openSingleViewInventory, this, LLUUID())); } LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) @@ -248,52 +252,116 @@ void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) { // save off copy of params mParams = params; - // Clear up the root view - // Note: This needs to be done *before* we build the new folder view - LLUUID root_id = getRootFolderID(); - if (mFolderRoot.get()) - { - removeItemID(root_id); - mFolderRoot.get()->destroyView(); - } - mCommitCallbackRegistrar.pushScope(); // registered as a widget; need to push callback scope ourselves - { - // Determine the root folder in case specified, and - // build the views starting with that folder. + initFolderRoot(); + + // Initialize base class params. + LLPanel::initFromParams(mParams); +} + +LLInventoryPanel::~LLInventoryPanel() +{ + U32 sort_order = getFolderViewModel()->getSorter().getSortOrder(); + if (mSortOrderSetting != INHERIT_SORT_ORDER) + { + gSavedSettings.setU32(mSortOrderSetting, sort_order); + } + + clearFolderRoot(); +} + +void LLInventoryPanel::initFolderRoot() +{ + // Clear up the root view + // Note: This needs to be done *before* we build the new folder view + LLUUID root_id = getRootFolderID(); + if (mFolderRoot.get()) + { + removeItemID(root_id); + mFolderRoot.get()->destroyView(); + } + + mCommitCallbackRegistrar.pushScope(); // registered as a widget; need to push callback scope ourselves + { + // Determine the root folder in case specified, and + // build the views starting with that folder. LLFolderView* folder_view = createFolderRoot(root_id); - mFolderRoot = folder_view->getHandle(); - - addItemID(root_id, mFolderRoot.get()); - } - mCommitCallbackRegistrar.popScope(); - mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); - mFolderRoot.get()->setEnableRegistrar(&mEnableCallbackRegistrar); - - // Scroller - LLRect scroller_view_rect = getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - LLScrollContainer::Params scroller_params(mParams.scroll()); - scroller_params.rect(scroller_view_rect); - mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params); - addChild(mScroller); - mScroller->addChild(mFolderRoot.get()); - mFolderRoot.get()->setScrollContainer(mScroller); - mFolderRoot.get()->setFollowsAll(); - mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); - - // Set up the callbacks from the inventory we're viewing, and then build everything. - mInventoryObserver = new LLInventoryPanelObserver(this); - mInventory->addObserver(mInventoryObserver); - - mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this)); - mInventory->addObserver(mCompletionObserver); - - if (mBuildViewsOnInit && mViewsInitialized == VIEWS_UNINITIALIZED) + mFolderRoot = folder_view->getHandle(); + mRootInited = true; + + addItemID(root_id, mFolderRoot.get()); + } + mCommitCallbackRegistrar.popScope(); + mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); + mFolderRoot.get()->setEnableRegistrar(&mEnableCallbackRegistrar); + + // Scroller + LLRect scroller_view_rect = getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(mParams.scroll()); + scroller_params.rect(scroller_view_rect); + mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params); + addChild(mScroller); + mScroller->addChild(mFolderRoot.get()); + mFolderRoot.get()->setScrollContainer(mScroller); + mFolderRoot.get()->setFollowsAll(); + mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + + if (mSelectionCallback) { + mFolderRoot.get()->setSelectCallback(mSelectionCallback); + } + + // Set up the callbacks from the inventory we're viewing, and then build everything. + mInventoryObserver = new LLInventoryPanelObserver(this); + mInventory->addObserver(mInventoryObserver); + + mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this)); + mInventory->addObserver(mCompletionObserver); + + if (mBuildViewsOnInit) + { + initializeViewBuilding(); + } + + if (mSortOrderSetting != INHERIT_SORT_ORDER) + { + setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); + } + else + { + setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); + } + + // hide inbox + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible")) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); + } + // hide marketplace listing box, unless we are a marketplace panel + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.use_marketplace_folders) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); + } + + // set the filter for the empty folder if the debug setting is on + if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) + { + getFilter().setFilterEmptySystemFolders(); + } + + // keep track of the clipboard state so that we avoid filtering too much + mClipboardState = LLClipboard::instance().getGeneration(); +} + +void LLInventoryPanel::initializeViewBuilding() +{ + if (mViewsInitialized == VIEWS_UNINITIALIZED) + { + LL_DEBUGS("Inventory") << "Setting views for " << getName() << " to initialize" << LL_ENDL; // Build view of inventory if we need default full hierarchy and inventory is ready, otherwise do in onIdle. // Initializing views takes a while so always do it onIdle if viewer already loaded. - if (mInventory->isInventoryUsable() + if (mInventory->isInventoryUsable() && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT) { // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect @@ -306,49 +374,6 @@ void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) gIdleCallbacks.addFunction(onIdle, (void*)this); } } - - if (mSortOrderSetting != INHERIT_SORT_ORDER) - { - setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); - } - else - { - setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); - } - - // hide inbox - if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible")) - { - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); - } - // hide marketplace listing box, unless we are a marketplace panel - if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.use_marketplace_folders) - { - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); - } - - // set the filter for the empty folder if the debug setting is on - if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) - { - getFilter().setFilterEmptySystemFolders(); - } - - // keep track of the clipboard state so that we avoid filtering too much - mClipboardState = LLClipboard::instance().getGeneration(); - - // Initialize base class params. - LLPanel::initFromParams(mParams); -} - -LLInventoryPanel::~LLInventoryPanel() -{ - U32 sort_order = getFolderViewModel()->getSorter().getSortOrder(); - if (mSortOrderSetting != INHERIT_SORT_ORDER) - { - gSavedSettings.setU32(mSortOrderSetting, sort_order); - } - - clearFolderRoot(); } /*virtual*/ @@ -356,8 +381,11 @@ void LLInventoryPanel::onVisibilityChange(BOOL new_visibility) { if (new_visibility && mViewsInitialized == VIEWS_UNINITIALIZED) { - mViewsInitialized = VIEWS_INITIALIZING; - gIdleCallbacks.addFunction(onIdle, (void*)this); + // first call can be from tab initialization + if (gFloaterView->getParentFloater(this) != NULL) + { + initializeViewBuilding(); + } } LLPanel::onVisibilityChange(new_visibility); } @@ -743,7 +771,7 @@ LLUUID LLInventoryPanel::getRootFolderID() LLStringExplicit label(mParams.start_folder.name()); setLabel(label); - root_id = gInventory.findCategoryUUIDForType(preferred_type, false); + root_id = gInventory.findCategoryUUIDForType(preferred_type); if (root_id.isNull()) { LL_WARNS() << "Could not find folder of type " << preferred_type << LL_ENDL; @@ -878,6 +906,7 @@ void LLInventoryPanel::idle(void* user_data) void LLInventoryPanel::initializeViews(F64 max_time) { if (!gInventory.isInventoryUsable()) return; + if (!mRootInited) return; mViewsInitialized = VIEWS_BUILDING; @@ -905,7 +934,10 @@ void LLInventoryPanel::initializeViews(F64 max_time) gIdleCallbacks.addFunction(idle, this); - openStartFolderOrMyInventory(); + if(mParams.open_first_folder) + { + openStartFolderOrMyInventory(); + } // Special case for new user login if (gAgent.isFirstLogin()) @@ -937,8 +969,8 @@ LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * br params.tool_tip = params.name; params.allow_drop = allow_drop; - params.font_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultColor)); - params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultHighlightColor)); + params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); + params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); return LLUICtrlFactory::create<LLFolderViewFolder>(params); } @@ -954,8 +986,8 @@ LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge params.rect = LLRect (0, 0, 0, 0); params.tool_tip = params.name; - params.font_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultColor)); - params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultHighlightColor)); + params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); + params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); return LLUICtrlFactory::create<LLFolderViewItem>(params); } @@ -1011,8 +1043,11 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, LLInventoryObject const* objectp, LLFolderViewItem *folder_view_item, LLFolderViewFolder *parent_folder, - const EBuildModes &mode) + const EBuildModes &mode, + S32 depth) { + depth++; + // Force the creation of an extra root level folder item if required by the inventory panel (default is "false") bool allow_drop = true; bool create_root = false; @@ -1042,7 +1077,7 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, if (objectp->getType() >= LLAssetType::AT_COUNT) { // Example: Happens when we add assets of new, not yet supported type to library - LL_DEBUGS() << "LLInventoryPanel::buildViewsTree called with unknown objectp->mType : " + LL_DEBUGS("Inventory") << "LLInventoryPanel::buildViewsTree called with unknown objectp->mType : " << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() << LL_ENDL; @@ -1112,7 +1147,8 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, } } - bool create_children = folder_view_item && objectp->getType() == LLAssetType::AT_CATEGORY; + bool create_children = folder_view_item && objectp->getType() == LLAssetType::AT_CATEGORY + && (mBuildChildrenViews || depth == 0); if (create_children) { @@ -1132,12 +1168,15 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, { create_children = false; // run it again for the sake of creating children - mBuildViewsQueue.push_back(id); + if (mBuildChildrenViews || depth == 0) + { + mBuildViewsQueue.push_back(id); + } } else { create_children = true; - folder_view_item->setChildrenInited(true); + folder_view_item->setChildrenInited(mBuildChildrenViews); } break; } @@ -1145,7 +1184,10 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, { create_children = false; // run it to create children, current caller is only interested in current view - mBuildViewsQueue.push_back(id); + if (mBuildChildrenViews || depth == 0) + { + mBuildViewsQueue.push_back(id); + } break; } case BUILD_ONE_FOLDER: @@ -1175,7 +1217,13 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, LLViewerInventoryItem::item_array_t* items; mInventory->lockDirectDescendentArrays(id, categories, items); + // Make sure panel won't lock in a loop over existing items if + // folder is enormous and at least some work gets done + const S32 MIN_ITEMS_PER_CALL = 500; + const S32 starting_item_count = mItemMap.size(); + LLFolderViewFolder *parentp = dynamic_cast<LLFolderViewFolder*>(folder_view_item); + bool done = true; if(categories) { @@ -1193,11 +1241,28 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, // each time, especially since content is growing, we can just // iter over copy of mItemMap in some way LLFolderViewItem* view_itemp = getItemByID(cat->getUUID()); - buildViewsTree(cat->getUUID(), id, cat, view_itemp, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode)); + buildViewsTree(cat->getUUID(), id, cat, view_itemp, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); } else { - buildViewsTree(cat->getUUID(), id, cat, NULL, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode)); + buildViewsTree(cat->getUUID(), id, cat, NULL, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); + } + } + + if (!mBuildChildrenViews + && mode == BUILD_TIMELIMIT + && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) + { + // Single folder view, check if we still have time + // + // Todo: make sure this causes no dupplciates, breaks nothing, + // especially filters and arrange + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildViewsQueue.push_back(id); + done = false; + break; } } } @@ -1217,10 +1282,33 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, // each time, especially since content is growing, we can just // iter over copy of mItemMap in some way LLFolderViewItem* view_itemp = getItemByID(item->getUUID()); - buildViewsTree(item->getUUID(), id, item, view_itemp, parentp, mode); + buildViewsTree(item->getUUID(), id, item, view_itemp, parentp, mode, depth); + } + + if (!mBuildChildrenViews + && mode == BUILD_TIMELIMIT + && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) + { + // Single folder view, check if we still have time + // + // Todo: make sure this causes no dupplciates, breaks nothing, + // especially filters and arrange + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildViewsQueue.push_back(id); + done = false; + break; + } } } } + + if (!mBuildChildrenViews && done) + { + // flat list is done initializing folder + folder_view_item->setChildrenInited(true); + } mInventory->unlockDirectDescendentArrays(id); } @@ -1285,6 +1373,37 @@ BOOL LLInventoryPanel::handleHover(S32 x, S32 y, MASK mask) return TRUE; } +BOOL LLInventoryPanel::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (const LLFolderViewItem* hover_item_p = (!mFolderRoot.isDead()) ? mFolderRoot.get()->getHoveredItem() : nullptr) + { + if (const LLFolderViewModelItemInventory* vm_item_p = static_cast<const LLFolderViewModelItemInventory*>(hover_item_p->getViewModelItem())) + { + LLSD params; + params["inv_type"] = vm_item_p->getInventoryType(); + params["thumbnail_id"] = vm_item_p->getThumbnailUUID(); + params["item_id"] = vm_item_p->getUUID(); + + // tooltip should only show over folder, but screen + // rect includes items under folder as well + LLRect actionable_rect = hover_item_p->calcScreenRect(); + if (hover_item_p->isOpen() && hover_item_p->hasVisibleChildren()) + { + actionable_rect.mBottom = actionable_rect.mTop - hover_item_p->getItemHeight(); + } + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hover_item_p->getToolTip()) + .sticky_rect(actionable_rect) + .delay_time(LLView::getTooltipTimeout()) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + return TRUE; + } + } + return LLPanel::handleToolTip(x, y, mask); +} + BOOL LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, @@ -1330,6 +1449,45 @@ void LLInventoryPanel::onFocusReceived() // inventory now handles cut/copy/paste/delete LLEditMenuHandler::gEditMenuHandler = mFolderRoot.get(); + // Tab support, when tabbing into this view, select first item + // (ideally needs to account for scroll) + bool select_first = mSelectThisID.isNull() && mFolderRoot.get() && mFolderRoot.get()->getSelectedCount() == 0; + + if (select_first) + { + LLFolderViewFolder::folders_t::const_iterator folders_it = mFolderRoot.get()->getFoldersBegin(); + LLFolderViewFolder::folders_t::const_iterator folders_end = mFolderRoot.get()->getFoldersEnd(); + + for (; folders_it != folders_end; ++folders_it) + { + const LLFolderViewFolder* folder_view = *folders_it; + if (folder_view->getVisible()) + { + const LLFolderViewModelItemInventory* modelp = static_cast<const LLFolderViewModelItemInventory*>(folder_view->getViewModelItem()); + setSelectionByID(modelp->getUUID(), TRUE); + select_first = false; + break; + } + } + } + + if (select_first) + { + LLFolderViewFolder::items_t::const_iterator items_it = mFolderRoot.get()->getItemsBegin(); + LLFolderViewFolder::items_t::const_iterator items_end = mFolderRoot.get()->getItemsEnd(); + + for (; items_it != items_end; ++items_it) + { + const LLFolderViewItem* item_view = *items_it; + if (item_view->getVisible()) + { + const LLFolderViewModelItemInventory* modelp = static_cast<const LLFolderViewModelItemInventory*>(item_view->getViewModelItem()); + setSelectionByID(modelp->getUUID(), TRUE); + break; + } + } + } + LLPanel::onFocusReceived(); } @@ -1380,6 +1538,7 @@ void LLInventoryPanel::setSelectCallback(const boost::function<void (const std:: { mFolderRoot.get()->setSelectCallback(cb); } + mSelectionCallback = cb; } void LLInventoryPanel::clearSelection() @@ -1426,6 +1585,10 @@ void LLInventoryPanel::onSelectionChange(const std::deque<LLFolderViewItem*>& it { fv->startRenamingSelectedItem(); } + else + { + LL_DEBUGS("Inventory") << "Failed to start renemr, no items selected" << LL_ENDL; + } } std::set<LLFolderViewItem*> selected_items = mFolderRoot.get()->getSelectionList(); @@ -1621,6 +1784,11 @@ void LLInventoryPanel::fileUploadLocation(const LLSD& userdata) } } +void LLInventoryPanel::openSingleViewInventory(LLUUID folder_id) +{ + LLPanelMainInventory::newFolderWindow(folder_id.isNull() ? LLFolderBridge::sSelf.get()->getUUID() : folder_id); +} + void LLInventoryPanel::purgeSelectedItems() { if (!mFolderRoot.get()) return; @@ -1754,22 +1922,49 @@ LLInventoryPanel* LLInventoryPanel::getActiveInventoryPanel(BOOL auto_open) } //static -void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const LLUUID& obj_id, BOOL main_panel, BOOL take_keyboard_focus, BOOL reset_filter) +void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const LLUUID& obj_id, BOOL use_main_panel, BOOL take_keyboard_focus, BOOL reset_filter) { LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel<LLSidepanelInventory>("inventory"); sidepanel_inventory->showInventoryPanel(); bool in_inbox = (gInventory.isObjectDescendentOf(obj_id, gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX))); - if (!in_inbox && (main_panel || !sidepanel_inventory->getMainInventoryPanel()->isRecentItemsPanelSelected())) + if (!in_inbox && (use_main_panel || !sidepanel_inventory->getMainInventoryPanel()->isRecentItemsPanelSelected())) { sidepanel_inventory->selectAllItemsPanel(); } + + LLFloater* inventory_floater = LLFloaterSidePanelContainer::getTopmostInventoryFloater(); + if(!auto_open && inventory_floater && inventory_floater->getVisible()) + { + LLSidepanelInventory *inventory_panel = inventory_floater->findChild<LLSidepanelInventory>("main_panel"); + LLPanelMainInventory* main_panel = inventory_panel->getMainInventoryPanel(); + if(main_panel->isSingleFolderMode() && main_panel->isGalleryViewMode()) + { + LL_DEBUGS("Inventory") << "Opening gallery panel for item" << obj_id << LL_ENDL; + main_panel->setGallerySelection(obj_id); + return; + } + } + + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && main_inventory->isSingleFolderMode() + && use_main_panel) + { + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj) + { + LL_DEBUGS("Inventory") << "Opening main inventory panel for item" << obj_id << LL_ENDL; + main_inventory->setSingleFolderViewRoot(obj->getParentUUID(), false); + main_inventory->setGallerySelection(obj_id); + return; + } + } LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(auto_open); if (active_panel) { - LL_DEBUGS("Messaging") << "Highlighting" << obj_id << LL_ENDL; + LL_DEBUGS("Messaging", "Inventory") << "Highlighting" << obj_id << LL_ENDL; if (reset_filter) { @@ -1788,7 +1983,7 @@ void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const L inventory_panel->setSelection(obj_id, take_keyboard_focus); } } - else + else if (auto_open) { LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); if (floater_inventory) @@ -1797,9 +1992,33 @@ void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const L } active_panel->setSelection(obj_id, take_keyboard_focus); } + else + { + // Created items are going to receive proper focus from callbacks + active_panel->setSelection(obj_id, take_keyboard_focus); + } } } +void LLInventoryPanel::setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id) +{ + + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + { + LLFloaterSidePanelContainer* inventory_floater = dynamic_cast<LLFloaterSidePanelContainer*>(*iter); + LLSidepanelInventory* sidepanel_inventory = inventory_floater->findChild<LLSidepanelInventory>("main_panel"); + + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && panel->hasAncestor(main_inventory) && !main_inventory->isSingleFolderMode()) + { + main_inventory->initSingleFolderRoot(folder_id); + main_inventory->toggleViewMode(); + main_inventory->setSingleFolderViewRoot(folder_id, false); + } + } +} + void LLInventoryPanel::addHideFolderType(LLFolderType::EType folder_type) { getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << folder_type)); @@ -2008,10 +2227,205 @@ LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params) mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER; } +static LLDefaultChildRegistry::Register<LLInventorySingleFolderPanel> t_single_folder_inventory_panel("single_folder_inventory_panel"); + +LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) + : LLInventoryPanel(params) +{ + mBuildChildrenViews = false; + getFilter().setSingleFolderMode(true); + getFilter().setEmptyLookupMessage("InventorySingleFolderNoMatches"); + getFilter().setDefaultEmptyLookupMessage("InventorySingleFolderEmpty"); + + mCommitCallbackRegistrar.replace("Inventory.DoToSelected", boost::bind(&LLInventorySingleFolderPanel::doToSelected, this, _2)); + mCommitCallbackRegistrar.replace("Inventory.DoCreate", boost::bind(&LLInventorySingleFolderPanel::doCreate, this, _2)); + mCommitCallbackRegistrar.replace("Inventory.Share", boost::bind(&LLInventorySingleFolderPanel::doShare, this)); +} + +LLInventorySingleFolderPanel::~LLInventorySingleFolderPanel() +{ +} + +void LLInventorySingleFolderPanel::initFromParams(const Params& p) +{ + mFolderID = gInventory.getRootFolderID(); + + mParams = p; + LLPanel::initFromParams(mParams); +} + +void LLInventorySingleFolderPanel::initFolderRoot(const LLUUID& start_folder_id) +{ + if(mRootInited) return; + + mRootInited = true; + if(start_folder_id.notNull()) + { + mFolderID = start_folder_id; + } + + mParams.open_first_folder = false; + mParams.start_folder.id = mFolderID; + + LLInventoryPanel::initFolderRoot(); + mFolderRoot.get()->setSingleFolderMode(true); +} + +void LLInventorySingleFolderPanel::changeFolderRoot(const LLUUID& new_id) +{ + if (mFolderID != new_id) + { + if(mFolderID.notNull()) + { + mBackwardFolders.push_back(mFolderID); + } + mFolderID = new_id; + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::onForwardFolder() +{ + if(isForwardAvailable()) + { + mBackwardFolders.push_back(mFolderID); + mFolderID = mForwardFolders.back(); + mForwardFolders.pop_back(); + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::onBackwardFolder() +{ + if(isBackwardAvailable()) + { + mForwardFolders.push_back(mFolderID); + mFolderID = mBackwardFolders.back(); + mBackwardFolders.pop_back(); + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::clearNavigationHistory() +{ + mForwardFolders.clear(); + mBackwardFolders.clear(); +} + +bool LLInventorySingleFolderPanel::isBackwardAvailable() +{ + return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back())); +} + +bool LLInventorySingleFolderPanel::isForwardAvailable() +{ + return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back())); +} + +boost::signals2::connection LLInventorySingleFolderPanel::setRootChangedCallback(root_changed_callback_t cb) +{ + return mRootChangedSignal.connect(cb); +} + +void LLInventorySingleFolderPanel::updateSingleFolderRoot() +{ + if (mFolderID != getRootFolderID()) + { + mRootChangedSignal(); + + LLUUID root_id = mFolderID; + if (mFolderRoot.get()) + { + mItemMap.clear(); + mFolderRoot.get()->destroyRoot(); + } + + mCommitCallbackRegistrar.pushScope(); + { + LLFolderView* folder_view = createFolderRoot(root_id); + folder_view->setChildrenInited(false); + mFolderRoot = folder_view->getHandle(); + mFolderRoot.get()->setSingleFolderMode(true); + addItemID(root_id, mFolderRoot.get()); + + LLRect scroller_view_rect = getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(mParams.scroll()); + scroller_params.rect(scroller_view_rect); + + if (mScroller) + { + removeChild(mScroller); + delete mScroller; + mScroller = NULL; + } + mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params); + addChild(mScroller); + mScroller->addChild(mFolderRoot.get()); + mFolderRoot.get()->setScrollContainer(mScroller); + mFolderRoot.get()->setFollowsAll(); + mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + + if (!mSelectionCallback.empty()) + { + mFolderRoot.get()->setSelectCallback(mSelectionCallback); + } + } + mCommitCallbackRegistrar.popScope(); + mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); + + buildNewViews(mFolderID); + + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if(root_floater) + { + root_floater->setFocus(true); + } + } +} + +bool LLInventorySingleFolderPanel::hasVisibleItems() +{ + return mFolderRoot.get()->hasVisibleChildren(); +} + +void LLInventorySingleFolderPanel::doCreate(const LLSD& userdata) +{ + std::string type_name = userdata.asString(); + LLUUID dest_id = LLFolderBridge::sSelf.get()->getUUID(); + if (("category" == type_name) || ("outfit" == type_name)) + { + changeFolderRoot(dest_id); + } + reset_inventory_filter(); + menu_create_inventory_item(this, dest_id, userdata); +} + +void LLInventorySingleFolderPanel::doToSelected(const LLSD& userdata) +{ + if (("open_in_current_window" == userdata.asString())) + { + changeFolderRoot(LLFolderBridge::sSelf.get()->getUUID()); + return; + } + LLInventoryPanel::doToSelected(userdata); +} + +void LLInventorySingleFolderPanel::doShare() +{ + LLAvatarActions::shareWithAvatars(this); +} /************************************************************************/ /* Asset Pre-Filtered Inventory Panel related class */ /************************************************************************/ +LLAssetFilteredInventoryPanel::LLAssetFilteredInventoryPanel(const Params& p) + : LLInventoryPanel(p) + , mAssetType(LLAssetType::AT_NONE) +{ +} + + void LLAssetFilteredInventoryPanel::initFromParams(const Params& p) { mAssetType = LLAssetType::lookup(p.filter_asset_type.getValue()); diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index 2c782a5ea7..341be0cf86 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -107,6 +107,7 @@ public: Optional<LLFolderView::Params> folder_view; Optional<LLFolderViewFolder::Params> folder; Optional<LLFolderViewItem::Params> item; + Optional<bool> open_first_folder; // All item and folder views will be initialized on init if true (default) // Will initialize on visibility change otherwise. @@ -126,6 +127,7 @@ public: show_root_folder("show_root_folder", false), allow_drop_on_root("allow_drop_on_root", true), use_marketplace_folders("use_marketplace_folders", false), + open_first_folder("open_first_folder", true), scroll("scroll"), accepts_drag_and_drop("accepts_drag_and_drop"), folder_view("folder_view"), @@ -159,22 +161,23 @@ public: LLFolderViewModelInventory& getRootViewModel() { return mInventoryViewModel; } // LLView methods - /*virtual*/ void onVisibilityChange(BOOL new_visibility); - void draw(); - /*virtual*/ BOOL handleKeyHere( KEY key, MASK mask ); - BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ void onVisibilityChange(BOOL new_visibility) override; + void draw() override; + /*virtual*/ BOOL handleKeyHere( KEY key, MASK mask ) override; + BOOL handleHover(S32 x, S32 y, MASK mask) override; /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, - std::string& tooltip_msg); + std::string& tooltip_msg) override; + BOOL handleToolTip(S32 x, S32 y, MASK mask) override; // LLUICtrl methods - /*virtual*/ void onFocusLost(); - /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost() override; + /*virtual*/ void onFocusReceived() override; void onFolderOpening(const LLUUID &id); // LLBadgeHolder methods - bool addBadge(LLBadge * badge); + bool addBadge(LLBadge * badge) override; // Call this method to set the selection. void openAllFolders(); @@ -211,6 +214,7 @@ public: LLUUID getRootFolderID(); LLScrollContainer* getScrollableContainer() { return mScroller; } bool getAllowDropOnRoot() { return mParams.allow_drop_on_root; } + bool areViewsInitialized() { return mViewsInitialized == VIEWS_INITIALIZED && mFolderRoot.get() && !mFolderRoot.get()->needsArrange(); } void onSelectionChange(const std::deque<LLFolderViewItem*> &items, BOOL user_action); @@ -221,6 +225,7 @@ public: void doCreate(const LLSD& userdata); bool beginIMSession(); void fileUploadLocation(const LLSD& userdata); + void openSingleViewInventory(LLUUID folder_id = LLUUID()); void purgeSelectedItems(); bool attachObject(const LLSD& userdata); static void idle(void* user_data); @@ -241,10 +246,10 @@ public: static void openInventoryPanelAndSetSelection(BOOL auto_open, const LLUUID& obj_id, - BOOL main_panel = FALSE, + BOOL use_main_panel = FALSE, BOOL take_keyboard_focus = TAKE_FOCUS_YES, BOOL reset_filter = FALSE); - + static void setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id); void addItemID(const LLUUID& id, LLFolderViewItem* itemp); void removeItemID(const LLUUID& id); LLFolderViewItem* getItemByID(const LLUUID& id); @@ -262,6 +267,10 @@ public: static void callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector<LLUUID> inventory_selected); + void changeFolderRoot(const LLUUID& new_id) {}; + void initFolderRoot(); + void initializeViewBuilding(); + protected: void openStartFolderOrMyInventory(); // open the first level of inventory void onItemsCompletion(); // called when selected items are complete @@ -298,6 +307,9 @@ protected: */ const LLInventoryFolderViewModelBuilder* mInvFVBridgeBuilder; + bool mBuildChildrenViews; // build root and children + bool mRootInited; + //-------------------------------------------------------------------- // Sorting @@ -357,6 +369,8 @@ protected: virtual LLFolderView * createFolderRoot(LLUUID root_id ); virtual LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); virtual LLFolderViewItem* createFolderViewItem(LLInvFVBridge * bridge); + + boost::function<void(const std::deque<LLFolderViewItem*>& items, BOOL user_action)> mSelectionCallback; private: // buildViewsTree does not include some checks and is meant // for recursive use, use buildNewViews() for first call @@ -365,7 +379,8 @@ private: LLInventoryObject const* objectp, LLFolderViewItem *target_view, LLFolderViewFolder *parent_folder_view, - const EBuildModes &mode); + const EBuildModes &mode, + S32 depth = -1); typedef enum e_views_initialization_state { @@ -381,6 +396,55 @@ private: std::deque<LLUUID> mBuildViewsQueue; }; + +class LLInventorySingleFolderPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block<Params, LLInventoryPanel::Params> + {}; + + void initFromParams(const Params& p); + bool isSelectionRemovable() { return false; } + + void initFolderRoot(const LLUUID& start_folder_id = LLUUID::null); + + void changeFolderRoot(const LLUUID& new_id); + void onForwardFolder(); + void onBackwardFolder(); + void clearNavigationHistory(); + LLUUID getSingleFolderRoot() { return mFolderID; } + + void doCreate(const LLSD& userdata); + void doToSelected(const LLSD& userdata); + void doShare(); + + bool isBackwardAvailable(); + bool isForwardAvailable(); + + bool hasVisibleItems(); + + void setNavBackwardList(std::list<LLUUID> backward_list) { mBackwardFolders = backward_list; } + void setNavForwardList(std::list<LLUUID> forward_list) { mForwardFolders = forward_list; } + std::list<LLUUID> getNavBackwardList() { return mBackwardFolders; } + std::list<LLUUID> getNavForwardList() { return mForwardFolders; } + + typedef boost::function<void()> root_changed_callback_t; + boost::signals2::connection setRootChangedCallback(root_changed_callback_t cb); + +protected: + LLInventorySingleFolderPanel(const Params& params); + ~LLInventorySingleFolderPanel(); + void updateSingleFolderRoot(); + + friend class LLUICtrlFactory; + + LLUUID mFolderID; + std::list<LLUUID> mBackwardFolders; + std::list<LLUUID> mForwardFolders; + + boost::signals2::signal<void()> mRootChangedSignal; +}; + /************************************************************************/ /* Asset Pre-Filtered Inventory Panel related class */ /* Exchanges filter's flexibility for speed of generation and */ @@ -400,7 +464,7 @@ public: void initFromParams(const Params& p); protected: - LLAssetFilteredInventoryPanel(const Params& p) : LLInventoryPanel(p) {} + LLAssetFilteredInventoryPanel(const Params& p); friend class LLUICtrlFactory; public: ~LLAssetFilteredInventoryPanel() {} diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index dd8c9b2dde..01496fa7ce 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -87,6 +87,7 @@ LLLoginInstance::LLLoginInstance() : mLoginModule(new LLLogin()), mNotifications(NULL), mLoginState("offline"), + mSaveMFA(true), mAttemptComplete(false), mTransferRate(0.0f), mDispatcher("LLLoginInstance", "change") @@ -449,10 +450,7 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) gViewerWindow->setShowProgress(FALSE); } - LLSD args(llsd::map( "MESSAGE", LLTrans::getString(response["message_id"]) )); - LLSD payload; - LLNotificationsUtil::add("PromptMFAToken", args, payload, - boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + showMFAChallange(LLTrans::getString(response["message_id"])); } else if( reason_response == "key" || reason_response == "presence" @@ -540,10 +538,7 @@ bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) { // SL-18511 this TOS failure happened while we are in the middle of an MFA challenge/response. // the previously entered token is very likely expired, so prompt again - LLSD args(llsd::map( "MESSAGE", LLTrans::getString("LoginFailedAuthenticationMFARequired") )); - LLSD payload; - LLNotificationsUtil::add("PromptMFAToken", args, payload, - boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + showMFAChallange(LLTrans::getString("LoginFailedAuthenticationMFARequired")); } else { @@ -561,6 +556,22 @@ bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) return true; } +void LLLoginInstance::showMFAChallange(const std::string& message) +{ + LLSD args(llsd::map("MESSAGE", message)); + LLSD payload; + if (gSavedSettings.getBOOL("RememberUser")) + { + LLNotificationsUtil::add("PromptMFATokenWithSave", args, payload, + boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + } + else + { + LLNotificationsUtil::add("PromptMFAToken", args, payload, + boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + } +} + bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & response) { bool continue_clicked = response["continue"].asBoolean(); @@ -576,6 +587,7 @@ bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & respon // Set the request data to true and retry login. mRequestData["params"]["token"] = token; + mSaveMFA = response.has("ignore") ? response["ignore"].asBoolean() : false; reconnect(); } else { LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL; diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index ee3ef0e4b1..2e9aab7c00 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -56,6 +56,7 @@ public: bool authSuccess() { return mAttemptComplete && mLoginState == "online"; } const std::string& getLoginState() { return mLoginState; } + bool saveMFA() const { return mSaveMFA; } LLSD getResponse(const std::string& key) { return getResponse()[key]; } LLSD getResponse(); @@ -84,6 +85,7 @@ private: void syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response); bool handleTOSResponse(bool v, const std::string& key); + void showMFAChallange(const std::string& message); bool handleMFAChallenge(LLSD const & notif, LLSD const & response); void attemptComplete() { mAttemptComplete = true; } // In the future an event? @@ -95,6 +97,7 @@ private: LLSD mRequestData; LLSD mResponseData; bool mAttemptComplete; + bool mSaveMFA; F64 mTransferRate; std::string mSerialNumber; int mLastExecEvent; diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index 6f3d40bb3a..8784f403cb 100644 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -700,10 +700,9 @@ void LLMarketplaceInventoryObserver::onIdleProcessQueue(void *userdata) // If it's a folder known to the marketplace, let's check it's in proper shape if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it)) { - LLInventoryCategory* cat = (LLInventoryCategory*)(obj); // can trigger notifyObservers // can cause more structural changes - validate_marketplacelistings(cat); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(obj->getUUID()); } } else @@ -897,7 +896,7 @@ void LLMarketplaceData::setDataFetchedSignal(const status_updated_signal_t::slot // Get/Post/Put requests to the SLM Server using the SLM API void LLMarketplaceData::getSLMListings() { - const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); setUpdating(marketplaceFolderId, true); LLCoros::instance().launch("getSLMListings", @@ -1804,7 +1803,7 @@ bool LLMarketplaceData::isUpdating(const LLUUID& folder_id, S32 depth) } else { - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); std::set<LLUUID>::iterator it = mPendingUpdateSet.find(marketplace_listings_uuid); if (it != mPendingUpdateSet.end()) { @@ -1848,8 +1847,7 @@ void LLMarketplaceData::decrementValidationWaiting(const LLUUID& folder_id, S32 if (found->second <= 0) { mValidationWaitingList.erase(found); - LLInventoryCategory *cat = gInventory.getCategory(folder_id); - validate_marketplacelistings(cat); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(folder_id); update_marketplace_category(folder_id); gInventory.notifyObservers(); } diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 602b7412a4..de988555c5 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -36,12 +36,11 @@ #include "llaccordionctrltab.h" #include "llappearancemgr.h" -#include "llagentbenefits.h" #include "llerror.h" #include "llfilepicker.h" #include "llfloaterperms.h" #include "llfloaterreg.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llimagedimensionsinfo.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" @@ -65,7 +64,6 @@ const S32 GALLERY_ITEMS_PER_ROW_MIN = 2; LLOutfitGallery::LLOutfitGallery(const LLOutfitGallery::Params& p) : LLOutfitListBase(), - mTexturesObserver(NULL), mOutfitsObserver(NULL), mScrollPanel(NULL), mGalleryPanel(NULL), @@ -125,7 +123,6 @@ void LLOutfitGallery::onOpen(const LLSD& info) LLOutfitListBase::onOpen(info); if (!mGalleryCreated) { - loadPhotos(); uuid_vec_t cats; getCurrentCategories(cats); int n = cats.size(); @@ -149,6 +146,264 @@ void LLOutfitGallery::draw() } } +BOOL LLOutfitGallery::handleKeyHere(KEY key, MASK mask) +{ + BOOL handled = FALSE; + switch (key) + { + case KEY_RETURN: + // Open selected items if enter key hit on the inventory panel + if (mask == MASK_NONE && mSelectedOutfitUUID.notNull()) + { + // Or should it wearSelectedOutfit? + getSelectedItem()->openOutfitsContent(); + } + handled = TRUE; + break; + case KEY_DELETE: +#if LL_DARWIN + case KEY_BACKSPACE: +#endif + // Delete selected items if delete or backspace key hit on the inventory panel + // Note: on Mac laptop keyboards, backspace and delete are one and the same + if (mSelectedOutfitUUID.notNull()) + { + onRemoveOutfit(mSelectedOutfitUUID); + } + handled = TRUE; + break; + + case KEY_F2: + LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); + handled = TRUE; + break; + + case KEY_PAGE_UP: + if (mScrollPanel) + { + mScrollPanel->pageUp(30); + } + handled = TRUE; + break; + + case KEY_PAGE_DOWN: + if (mScrollPanel) + { + mScrollPanel->pageDown(30); + } + handled = TRUE; + break; + + case KEY_HOME: + if (mScrollPanel) + { + mScrollPanel->goToTop(); + } + handled = TRUE; + break; + + case KEY_END: + if (mScrollPanel) + { + mScrollPanel->goToBottom(); + } + handled = TRUE; + break; + + case KEY_LEFT: + moveLeft(); + handled = TRUE; + break; + + case KEY_RIGHT: + moveRight(); + handled = TRUE; + break; + + case KEY_UP: + moveUp(); + handled = TRUE; + break; + + case KEY_DOWN: + moveDown(); + handled = TRUE; + break; + + default: + break; + } + + if (handled) + { + mOutfitGalleryMenu->hide(); + } + + return handled; +} + +void LLOutfitGallery::moveUp() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n -= mItemsInRow; + if (n >= 0) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } + } +} + +void LLOutfitGallery::moveDown() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n += mItemsInRow; + if (n < mItemsAddedCount) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } + } +} + +void LLOutfitGallery::moveLeft() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + // Might be better to get item from panel + S32 n = mItemIndexMap[item]; + n--; + if (n < 0) + { + n = mItemsAddedCount - 1; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } +} + +void LLOutfitGallery::moveRight() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n++; + if (n == mItemsAddedCount) + { + n = 0; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } +} + +void LLOutfitGallery::onFocusLost() +{ + LLOutfitListBase::onFocusLost(); + + if (mSelectedOutfitUUID.notNull()) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + item->setSelected(false); + } + } +} + +void LLOutfitGallery::onFocusReceived() +{ + LLOutfitListBase::onFocusReceived(); + + if (mSelectedOutfitUUID.notNull()) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + item->setSelected(true); + } + } +} + +void LLOutfitGallery::onRemoveOutfit(const LLUUID& outfit_cat_id) +{ + LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(onOutfitsRemovalConfirmation, _1, _2, outfit_cat_id)); +} + +void LLOutfitGallery::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + if (outfit_cat_id.notNull()) + { + gInventory.removeCategory(outfit_cat_id); + } +} + +void LLOutfitGallery::scrollToShowItem(const LLUUID& item_id) +{ + LLOutfitGalleryItem* item = mOutfitMap[item_id]; + if (item) + { + const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect(); + + LLRect item_rect; + item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel); + LLRect overlap_rect(item_rect); + overlap_rect.intersectWith(visible_content_rect); + + //Scroll when the selected item is outside the visible area + if (overlap_rect.getHeight() + 5 < item->getRect().getHeight()) + { + LLRect content_rect = mScrollPanel->getContentWindowRect(); + LLRect constraint_rect; + constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); + + LLRect item_doc_rect; + item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel); + + mScrollPanel->scrollToShowRect(item_doc_rect, constraint_rect); + } + } +} + void LLOutfitGallery::updateRowsIfNeeded() { if(((getRect().getWidth() - mRowPanelWidth) > mItemWidth) && mRowCount > 1) @@ -269,8 +524,9 @@ void LLOutfitGallery::addToGallery(LLOutfitGalleryItem* item) mHiddenItems.push_back(item); return; } + mItemIndexMap[item] = mItemsAddedCount; + mIndexToItemMap[mItemsAddedCount] = item; mItemsAddedCount++; - mItemIndexMap[item] = mItemsAddedCount - 1; int n = mItemsAddedCount; int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; int n_prev = n - 1; @@ -306,6 +562,7 @@ void LLOutfitGallery::removeFromGalleryLast(LLOutfitGalleryItem* item) int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; mItemsAddedCount--; + mIndexToItemMap.erase(mItemsAddedCount); bool remove_row = row_count != row_count_prev; removeFromLastRow(mItems[mItemsAddedCount]); @@ -331,6 +588,7 @@ void LLOutfitGallery::removeFromGalleryMiddle(LLOutfitGalleryItem* item) } int n = mItemIndexMap[item]; mItemIndexMap.erase(item); + mIndexToItemMap.erase(n); std::vector<LLOutfitGalleryItem*> saved; for (int i = mItemsAddedCount - 1; i > n; i--) { @@ -364,9 +622,15 @@ LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID gitem->setFollowsTop(); gitem->setOutfitName(name); gitem->setUUID(outfit_id); + gitem->setGallery(this); return gitem; } +LLOutfitGalleryItem* LLOutfitGallery::getSelectedItem() +{ + return mOutfitMap[mSelectedOutfitUUID]; +} + void LLOutfitGallery::buildGalleryPanel(int row_count) { LLPanel::Params params; @@ -440,12 +704,6 @@ void LLOutfitGallery::moveRowPanel(LLPanel* stack, int left, int bottom) LLOutfitGallery::~LLOutfitGallery() { delete mOutfitGalleryMenu; - - if (gInventory.containsObserver(mTexturesObserver)) - { - gInventory.removeObserver(mTexturesObserver); - } - delete mTexturesObserver; if (gInventory.containsObserver(mOutfitsObserver)) { @@ -617,6 +875,7 @@ void LLOutfitGallery::onChangeOutfitSelection(LLWearableItemsList* list, const L { mOutfitMap[category_id]->setSelected(TRUE); } + // mSelectedOutfitUUID will be set in LLOutfitListBase::ChangeOutfitSelection } void LLOutfitGallery::wearSelectedOutfit() @@ -668,7 +927,8 @@ static LLDefaultChildRegistry::Register<LLOutfitGalleryItem> r("outfit_gallery_i LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) : LLPanel(p), - mTexturep(NULL), + mGallery(nullptr), + mTexturep(nullptr), mSelected(false), mWorn(false), mDefaultImage(true), @@ -743,10 +1003,13 @@ void LLOutfitGalleryItem::setOutfitWorn(bool value) mWorn = value; LLStringUtil::format_map_t worn_string_args; std::string worn_string = getString("worn_string", worn_string_args); - LLUIColor text_color = LLUIColorTable::instance().getColor(mSelected ? "White" : (mWorn ? "OutfitGalleryItemWorn" : "White"), LLColor4::white); + LLUIColor text_color = LLUIColorTable::instance().getColor("White", LLColor4::white); mOutfitWornText->setReadOnlyColor(text_color.get()); mOutfitNameText->setReadOnlyColor(text_color.get()); + mOutfitWornText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); + mOutfitNameText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitWornText->setValue(value ? worn_string : ""); + mOutfitNameText->setText(mOutfitName); // refresh LLTextViewModel to pick up font changes } void LLOutfitGalleryItem::setSelected(bool value) @@ -770,8 +1033,63 @@ BOOL LLOutfitGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) BOOL LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) { + return openOutfitsContent() || LLPanel::handleDoubleClick(x, y, mask); +} + +BOOL LLOutfitGalleryItem::handleKeyHere(KEY key, MASK mask) +{ + if (!mGallery) + { + return FALSE; + } + + BOOL handled = FALSE; + switch (key) + { + case KEY_LEFT: + mGallery->moveLeft(); + handled = true; + break; + + case KEY_RIGHT: + mGallery->moveRight(); + handled = true; + break; + + case KEY_UP: + mGallery->moveUp(); + handled = true; + break; + + case KEY_DOWN: + mGallery->moveDown(); + handled = true; + break; + + default: + break; + } + return handled; +} + +void LLOutfitGalleryItem::onFocusLost() +{ + setSelected(false); + + LLPanel::onFocusLost(); +} + +void LLOutfitGalleryItem::onFocusReceived() +{ + setSelected(true); + + LLPanel::onFocusReceived(); +} + +bool LLOutfitGalleryItem::openOutfitsContent() +{ LLTabContainer* appearence_tabs = LLPanelOutfitsInventory::findInstance()->getChild<LLTabContainer>("appearance_tabs"); - if (appearence_tabs && (mUUID != LLUUID())) + if (appearence_tabs && mUUID.notNull()) { appearence_tabs->selectTabByName("outfitslist_tab"); LLPanel* panel = appearence_tabs->getCurrentPanel(); @@ -784,12 +1102,11 @@ BOOL LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) outfit_list->setSelectedOutfitByUUID(mUUID); LLAccordionCtrlTab* tab = accordion->getSelectedTab(); tab->showAndFocusHeader(); - return TRUE; + return true; } } } - - return LLPanel::handleDoubleClick(x, y, mask); + return false; } bool LLOutfitGalleryItem::setImageAssetId(LLUUID image_asset_id) @@ -835,68 +1152,22 @@ LLContextMenu* LLOutfitGalleryContextMenu::createMenu() boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.Edit", boost::bind(editOutfit)); registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); - registrar.add("Outfit.Delete", boost::bind(&LLOutfitGalleryContextMenu::onRemoveOutfit, this, selected_id)); + registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); - registrar.add("Outfit.UploadPhoto", boost::bind(&LLOutfitGalleryContextMenu::onUploadPhoto, this, selected_id)); - registrar.add("Outfit.SelectPhoto", boost::bind(&LLOutfitGalleryContextMenu::onSelectPhoto, this, selected_id)); - registrar.add("Outfit.TakeSnapshot", boost::bind(&LLOutfitGalleryContextMenu::onTakeSnapshot, this, selected_id)); - registrar.add("Outfit.RemovePhoto", boost::bind(&LLOutfitGalleryContextMenu::onRemovePhoto, this, selected_id)); + registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id)); enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2)); enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitGalleryContextMenu::onVisible, this, _2)); return createFromFile("menu_gallery_outfit_tab.xml"); } -void LLOutfitGalleryContextMenu::onUploadPhoto(const LLUUID& outfit_cat_id) -{ - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - if (gallery && outfit_cat_id.notNull()) - { - gallery->uploadPhoto(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onSelectPhoto(const LLUUID& outfit_cat_id) -{ - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - if (gallery && outfit_cat_id.notNull()) - { - gallery->onSelectPhoto(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onRemovePhoto(const LLUUID& outfit_cat_id) -{ - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - if (gallery && outfit_cat_id.notNull()) - { - gallery->checkRemovePhoto(outfit_cat_id); - gallery->refreshOutfit(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onTakeSnapshot(const LLUUID& outfit_cat_id) +void LLOutfitGalleryContextMenu::onThumbnail(const LLUUID& outfit_cat_id) { LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); if (gallery && outfit_cat_id.notNull()) { - gallery->onTakeSnapshot(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onRemoveOutfit(const LLUUID& outfit_cat_id) -{ - LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitGalleryContextMenu::onOutfitsRemovalConfirmation, this, _1, _2, outfit_cat_id)); -} - -void LLOutfitGalleryContextMenu::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; // canceled - - if (outfit_cat_id.notNull()) - { - gInventory.removeCategory(outfit_cat_id); + LLSD data(outfit_cat_id); + LLFloaterReg::showInstance("change_item_thumbnail", data); } } @@ -919,16 +1190,6 @@ bool LLOutfitGalleryContextMenu::onEnable(LLSD::String param) bool LLOutfitGalleryContextMenu::onVisible(LLSD::String param) { - mMenuHandle.get()->getChild<LLUICtrl>("upload_photo")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); - if ("remove_photo" == param) - { - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - LLUUID selected_id = mUUIDs.front(); - if (gallery && selected_id.notNull()) - { - return !gallery->hasDefaultImage(selected_id); - } - } return LLOutfitContextMenu::onVisible(param); } @@ -943,56 +1204,12 @@ void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() bool have_selection = getSelectedOutfitID().notNull(); mMenu->setItemVisible("expand", FALSE); mMenu->setItemVisible("collapse", FALSE); - mMenu->setItemVisible("upload_photo", have_selection); - mMenu->setItemVisible("select_photo", have_selection); - mMenu->setItemVisible("take_snapshot", have_selection); - mMenu->setItemVisible("remove_photo", !hasDefaultImage()); + mMenu->setItemVisible("thumbnail", have_selection); mMenu->setItemVisible("sepatator3", TRUE); mMenu->setItemVisible("sort_folders_by_name", TRUE); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); } -void LLOutfitGalleryGearMenu::onUploadFoto() -{ - LLUUID selected_outfit_id = getSelectedOutfitID(); - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - if (gallery && selected_outfit_id.notNull()) - { - gallery->uploadPhoto(selected_outfit_id); - } -} - -void LLOutfitGalleryGearMenu::onSelectPhoto() -{ - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && !selected_outfit_id.isNull()) - { - gallery->onSelectPhoto(selected_outfit_id); - } -} - -void LLOutfitGalleryGearMenu::onRemovePhoto() -{ - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && !selected_outfit_id.isNull()) - { - gallery->checkRemovePhoto(selected_outfit_id); - gallery->refreshOutfit(selected_outfit_id); - } -} - -void LLOutfitGalleryGearMenu::onTakeSnapshot() -{ - LLOutfitGallery* gallery = dynamic_cast<LLOutfitGallery*>(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && !selected_outfit_id.isNull()) - { - gallery->onTakeSnapshot(selected_outfit_id); - } -} - void LLOutfitGalleryGearMenu::onChangeSortOrder() { bool sort_by_name = !gSavedSettings.getBOOL("OutfitGallerySortByName"); @@ -1019,240 +1236,89 @@ void LLOutfitGallery::onTextureSelectionChanged(LLInventoryItem* itemp) { } -void LLOutfitGallery::loadPhotos() -{ - //Iterate over inventory - mSnapshotFolderID = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); - LLViewerInventoryCategory* textures_category = gInventory.getCategory(mSnapshotFolderID); - if (!textures_category) - return; - if (mTexturesObserver == NULL) - { - mTexturesObserver = new LLInventoryCategoriesObserver(); - gInventory.addObserver(mTexturesObserver); - } - - // Start observing changes in "Textures" category. - mTexturesObserver->addCategory(mSnapshotFolderID, - boost::bind(&LLOutfitGallery::refreshTextures, this, mSnapshotFolderID)); - - textures_category->fetch(); -} - -void LLOutfitGallery::updateSnapshotFolderObserver() -{ - if(mSnapshotFolderID != gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE)) - { - if (gInventory.containsObserver(mTexturesObserver)) - { - gInventory.removeObserver(mTexturesObserver); - } - delete mTexturesObserver; - mTexturesObserver = NULL; - loadPhotos(); - } -} - void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) { LLViewerInventoryCategory* category = gInventory.getCategory(category_id); if (category) { bool photo_loaded = false; - LLInventoryModel::cat_array_t sub_cat_array; - LLInventoryModel::item_array_t outfit_item_array; - // Collect all sub-categories of a given category. - gInventory.collectDescendents( - category->getUUID(), - sub_cat_array, - outfit_item_array, - LLInventoryModel::EXCLUDE_TRASH); - BOOST_FOREACH(LLViewerInventoryItem* outfit_item, outfit_item_array) + LLUUID asset_id = category->getThumbnailUUID(); + if (asset_id.isNull()) { - LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); - LLUUID asset_id, inv_id; - std::string item_name; - if (linked_item != NULL) + LLInventoryModel::cat_array_t sub_cat_array; + LLInventoryModel::item_array_t outfit_item_array; + // Collect all sub-categories of a given category. + gInventory.collectDescendents( + category->getUUID(), + sub_cat_array, + outfit_item_array, + LLInventoryModel::EXCLUDE_TRASH); + BOOST_FOREACH(LLViewerInventoryItem* outfit_item, outfit_item_array) { - if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) + LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); + LLUUID asset_id, inv_id; + std::string item_name; + if (linked_item != NULL) { - asset_id = linked_item->getAssetUUID(); - inv_id = linked_item->getUUID(); - item_name = linked_item->getName(); + if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + asset_id = linked_item->getAssetUUID(); + inv_id = linked_item->getUUID(); + item_name = linked_item->getName(); + } } - } - else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) - { - asset_id = outfit_item->getAssetUUID(); - inv_id = outfit_item->getUUID(); - item_name = outfit_item->getName(); - } - if (asset_id.notNull()) - { - photo_loaded |= mOutfitMap[category_id]->setImageAssetId(asset_id); - // Rename links - if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) + else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) + { + asset_id = outfit_item->getAssetUUID(); + inv_id = outfit_item->getUUID(); + item_name = outfit_item->getName(); + } + if (category->getThumbnailUUID().notNull()) + { + asset_id = category->getThumbnailUUID(); + } + if (asset_id.notNull()) { - LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); - LLStringUtil::format_map_t photo_string_args; - photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); - std::string new_name = getString("outfit_photo_string", photo_string_args); - LLSD updates; - updates["name"] = new_name; - update_inventory_item(inv_id, updates, NULL); - mOutfitRenamePending.setNull(); - LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); - if (appearance_floater) + photo_loaded |= mOutfitMap[category_id]->setImageAssetId(asset_id); + // Rename links + if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) + { + LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); + LLStringUtil::format_map_t photo_string_args; + photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); + std::string new_name = getString("outfit_photo_string", photo_string_args); + LLSD updates; + updates["name"] = new_name; + update_inventory_item(inv_id, updates, NULL); + mOutfitRenamePending.setNull(); + LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); + if (appearance_floater) + { + appearance_floater->setFocus(TRUE); + } + } + if (item_name == LLAppearanceMgr::sExpectedTextureName) { - appearance_floater->setFocus(TRUE); + // Images with "appropriate" name take priority + break; } } - if (item_name == LLAppearanceMgr::sExpectedTextureName) + if (!photo_loaded) { - // Images with "appropriate" name take priority - break; + mOutfitMap[category_id]->setDefaultImage(); } } - if (!photo_loaded) - { - mOutfitMap[category_id]->setDefaultImage(); - } - } - } - - if (mGalleryCreated && !LLApp::isExiting()) - { - reArrangeRows(); - } -} - -// Refresh linked textures from "textures" uploads folder -void LLOutfitGallery::refreshTextures(const LLUUID& category_id) -{ - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - - // Collect all sub-categories of a given category. - LLIsType is_texture(LLAssetType::AT_TEXTURE); - gInventory.collectDescendentsIf( - category_id, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - is_texture); - - //Find texture which contain pending outfit ID string in name - LLViewerInventoryItem* photo_upload_item = NULL; - BOOST_FOREACH(LLViewerInventoryItem* item, item_array) - { - std::string name = item->getName(); - if (!mOutfitLinkPending.isNull() && name == mOutfitLinkPending.asString()) - { - photo_upload_item = item; - break; - } - } - - if (photo_upload_item != NULL) - { - LLUUID photo_item_id = photo_upload_item->getUUID(); - LLInventoryObject* upload_object = gInventory.getObject(photo_item_id); - if (!upload_object) - { - LL_WARNS() << "LLOutfitGallery::refreshTextures added_object is null!" << LL_ENDL; } else { - linkPhotoToOutfit(photo_item_id, mOutfitLinkPending); - mOutfitRenamePending = mOutfitLinkPending; - mOutfitLinkPending.setNull(); + mOutfitMap[category_id]->setImageAssetId(asset_id); } } -} - -void LLOutfitGallery::uploadPhoto(LLUUID outfit_id) -{ - outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); - if (outfit_it == mOutfitMap.end() || outfit_it->first.isNull()) - { - return; - } - (new LLFilePickerReplyThread(boost::bind(&LLOutfitGallery::uploadOutfitImage, this, _1, outfit_id), LLFilePicker::FFLOAD_IMAGE, false))->getFile(); -} - -void LLOutfitGallery::uploadOutfitImage(const std::vector<std::string>& filenames, LLUUID outfit_id) -{ - std::string filename = filenames[0]; - LLLocalBitmap* unit = new LLLocalBitmap(filename); - if (unit->getValid()) + + if (mGalleryCreated && !LLApp::isExiting()) { - std::string exten = gDirUtilp->getExtension(filename); - U32 codec = LLImageBase::getCodecFromExtension(exten); - - LLImageDimensionsInfo image_info; - std::string image_load_error; - if (!image_info.load(filename, codec)) - { - image_load_error = image_info.getLastError(); - } - - S32 max_width = MAX_OUTFIT_PHOTO_WIDTH; - S32 max_height = MAX_OUTFIT_PHOTO_HEIGHT; - - if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) - { - LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); - - image_load_error = LLTrans::getString("outfit_photo_load_dimensions_error", args); - } - - if (!image_load_error.empty()) - { - LLSD subst; - subst["REASON"] = image_load_error; - LLNotificationsUtil::add("OutfitPhotoLoadError", subst); - return; - } - - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - void *nruserdata = NULL; - nruserdata = (void *)&outfit_id; - - LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(outfit_id); - if (!outfit_cat) return; - updateSnapshotFolderObserver(); - checkRemovePhoto(outfit_id); - std::string upload_pending_name = outfit_id.asString(); - std::string upload_pending_desc = ""; - upload_new_resource(filename, // file - upload_pending_name, - upload_pending_desc, - 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - upload_pending_name, LLAssetStorage::LLStoreAssetCallback(), expected_upload_cost, nruserdata, false); - mOutfitLinkPending = outfit_id; + reArrangeRows(); } - delete unit; -} - -void LLOutfitGallery::linkPhotoToOutfit(LLUUID photo_id, LLUUID outfit_id) -{ - LLPointer<LLInventoryCallback> cb = new LLUpdateGalleryOnPhotoLinked(); - link_inventory_object(outfit_id, photo_id, cb); -} - -bool LLOutfitGallery::checkRemovePhoto(LLUUID outfit_id) -{ - LLAppearanceMgr::instance().removeOutfitPhoto(outfit_id); - return true; -} - -void LLUpdateGalleryOnPhotoLinked::fire(const LLUUID& inv_item_id) -{ } LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) @@ -1270,151 +1336,3 @@ LLUUID LLOutfitGallery::getDefaultPhoto() return LLUUID(); } -void LLOutfitGallery::onTexturePickerCommit(LLTextureCtrl::ETexturePickOp op, LLUUID id) -{ - LLUUID selected_outfit_id = getSelectedOutfitUUID(); - - if (selected_outfit_id.isNull()) - { - return; - } - - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - - if (floaterp && op == LLTextureCtrl::TEXTURE_SELECT) - { - LLUUID image_item_id; - if (id.notNull()) - { - image_item_id = id; - } - else - { - image_item_id = floaterp->findItemID(floaterp->getAssetID(), FALSE, TRUE); - if (image_item_id.isNull()) - { - LL_WARNS() << "id or image_item_id is NULL!" << LL_ENDL; - return; - } - } - - std::string image_load_error; - S32 max_width = MAX_OUTFIT_PHOTO_WIDTH; - S32 max_height = MAX_OUTFIT_PHOTO_HEIGHT; - if (mTextureSelected.isNull() || - mTextureSelected->getFullWidth() == 0 || - mTextureSelected->getFullHeight() == 0) - { - image_load_error = LLTrans::getString("outfit_photo_verify_dimensions_error"); - LL_WARNS() << "Cannot verify selected texture dimensions" << LL_ENDL; - return; - } - S32 width = mTextureSelected->getFullWidth(); - S32 height = mTextureSelected->getFullHeight(); - if ((width > max_width) || (height > max_height)) - { - LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); - - image_load_error = LLTrans::getString("outfit_photo_select_dimensions_error", args); - } - - if (!image_load_error.empty()) - { - LLSD subst; - subst["REASON"] = image_load_error; - LLNotificationsUtil::add("OutfitPhotoLoadError", subst); - return; - } - - checkRemovePhoto(selected_outfit_id); - linkPhotoToOutfit(image_item_id, selected_outfit_id); - } -} - -void LLOutfitGallery::onSelectPhoto(LLUUID selected_outfit_id) -{ - if (selected_outfit_id.notNull()) - { - - // show hourglass cursor when loading inventory window - // because inventory construction is slooow - getWindow()->setCursor(UI_CURSOR_WAIT); - LLFloater* floaterp = mFloaterHandle.get(); - - // Show the dialog - if (floaterp) - { - floaterp->openFloater(); - } - else - { - floaterp = new LLFloaterTexturePicker( - this, - getPhotoAssetId(selected_outfit_id), - getPhotoAssetId(selected_outfit_id), - getPhotoAssetId(selected_outfit_id), - FALSE, - TRUE, - "SELECT PHOTO", - PERM_NONE, - PERM_NONE, - PERM_NONE, - FALSE, - NULL); - - mFloaterHandle = floaterp->getHandle(); - mTextureSelected = NULL; - - LLFloaterTexturePicker* texture_floaterp = dynamic_cast<LLFloaterTexturePicker*>(floaterp); - if (texture_floaterp) - { - texture_floaterp->setTextureSelectedCallback(boost::bind(&LLOutfitGallery::onTextureSelectionChanged, this, _1)); - texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLOutfitGallery::onTexturePickerCommit, this, _1, _2)); - texture_floaterp->setOnUpdateImageStatsCallback(boost::bind(&LLOutfitGallery::onTexturePickerUpdateImageStats, this, _1)); - texture_floaterp->setLocalTextureEnabled(FALSE); - texture_floaterp->setBakeTextureEnabled(FALSE); - texture_floaterp->setCanApply(false, true); - } - - floaterp->openFloater(); - } - floaterp->setFocus(TRUE); - } -} - -void LLOutfitGallery::onTakeSnapshot(LLUUID selected_outfit_id) -{ - LLFloaterReg::toggleInstanceOrBringToFront("simple_outfit_snapshot"); - LLFloaterSimpleOutfitSnapshot* snapshot_floater = LLFloaterSimpleOutfitSnapshot::getInstance(); - if (snapshot_floater) - { - snapshot_floater->setOutfitID(selected_outfit_id); - snapshot_floater->getInstance()->setGallery(this); - } -} - -void LLOutfitGallery::onBeforeOutfitSnapshotSave() -{ - LLUUID selected_outfit_id = getSelectedOutfitUUID(); - if (!selected_outfit_id.isNull()) - { - checkRemovePhoto(selected_outfit_id); - updateSnapshotFolderObserver(); - } -} - -void LLOutfitGallery::onAfterOutfitSnapshotSave() -{ - LLUUID selected_outfit_id = getSelectedOutfitUUID(); - if (!selected_outfit_id.isNull()) - { - mOutfitLinkPending = selected_outfit_id; - } -} - -void LLOutfitGallery::onTexturePickerUpdateImageStats(LLPointer<LLViewerTexture> texture) -{ - mTextureSelected = texture; -} diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index ce5c090134..9915752962 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -33,7 +33,6 @@ #include "lllayoutstack.h" #include "lloutfitslist.h" #include "llpanelappearancetab.h" -#include "lltexturectrl.h" #include "llviewertexture.h" #include <vector> @@ -44,15 +43,6 @@ class LLOutfitListGearMenuBase; class LLOutfitGalleryGearMenu; class LLOutfitGalleryContextMenu; -class LLUpdateGalleryOnPhotoLinked : public LLInventoryCallback -{ -public: - LLUpdateGalleryOnPhotoLinked(){} - virtual ~LLUpdateGalleryOnPhotoLinked(){} - /* virtual */ void fire(const LLUUID& inv_item_id); -private: -}; - class LLOutfitGallery : public LLOutfitListBase { public: @@ -83,10 +73,19 @@ public: /*virtual*/ BOOL postBuild(); /*virtual*/ void onOpen(const LLSD& info); - /*virtual*/ void draw(); - - void onSelectPhoto(LLUUID selected_outfit_id); - void onTakeSnapshot(LLUUID selected_outfit_id); + /*virtual*/ void draw(); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); + void moveUp(); + void moveDown(); + void moveLeft(); + void moveRight(); + + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + + static void onRemoveOutfit(const LLUUID& outfit_cat_id); + static void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id); + void scrollToShowItem(const LLUUID& item_id); void wearSelectedOutfit(); @@ -106,14 +105,8 @@ public: void updateMessageVisibility(); bool hasDefaultImage(const LLUUID& outfit_cat_id); - void refreshTextures(const LLUUID& category_id); void refreshOutfit(const LLUUID& category_id); - void onTexturePickerCommit(LLTextureCtrl::ETexturePickOp op, LLUUID id); - void onTexturePickerUpdateImageStats(LLPointer<LLViewerTexture> texture); - void onBeforeOutfitSnapshotSave(); - void onAfterOutfitSnapshotSave(); - protected: /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); /*virtual*/ void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid); @@ -127,14 +120,8 @@ protected: void applyFilter(LLOutfitGalleryItem* item, const std::string& filter_substring); private: - void loadPhotos(); - void uploadPhoto(LLUUID outfit_id); - void uploadOutfitImage(const std::vector<std::string>& filenames, LLUUID outfit_id); - void updateSnapshotFolderObserver(); LLUUID getPhotoAssetId(const LLUUID& outfit_id); LLUUID getDefaultPhoto(); - void linkPhotoToOutfit(LLUUID outfit_id, LLUUID photo_id); - bool checkRemovePhoto(LLUUID outfit_id); void addToGallery(LLOutfitGalleryItem* item); void removeFromGalleryLast(LLOutfitGalleryItem* item); void removeFromGalleryMiddle(LLOutfitGalleryItem* item); @@ -150,6 +137,7 @@ private: void updateGalleryWidth(); LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id); + LLOutfitGalleryItem* getSelectedItem(); void onTextureSelectionChanged(LLInventoryItem* itemp); @@ -190,17 +178,15 @@ private: LLListContextMenu* mOutfitGalleryMenu; - LLHandle<LLFloater> mFloaterHandle; - typedef std::map<LLUUID, LLOutfitGalleryItem*> outfit_map_t; typedef outfit_map_t::value_type outfit_map_value_t; outfit_map_t mOutfitMap; - typedef std::map<LLOutfitGalleryItem*, int> item_num_map_t; + typedef std::map<LLOutfitGalleryItem*, S32> item_num_map_t; typedef item_num_map_t::value_type item_numb_map_value_t; item_num_map_t mItemIndexMap; + std::map<S32, LLOutfitGalleryItem*> mIndexToItemMap; - LLInventoryCategoriesObserver* mTexturesObserver; LLInventoryCategoriesObserver* mOutfitsObserver; }; class LLOutfitGalleryContextMenu : public LLOutfitContextMenu @@ -211,17 +197,13 @@ public: LLOutfitGalleryContextMenu(LLOutfitListBase* outfit_list) : LLOutfitContextMenu(outfit_list), mOutfitList(outfit_list){} + protected: /* virtual */ LLContextMenu* createMenu(); bool onEnable(LLSD::String param); bool onVisible(LLSD::String param); - void onUploadPhoto(const LLUUID& outfit_cat_id); - void onSelectPhoto(const LLUUID& outfit_cat_id); - void onRemovePhoto(const LLUUID& outfit_cat_id); - void onTakeSnapshot(const LLUUID& outfit_cat_id); + void onThumbnail(const LLUUID& outfit_cat_id); void onCreate(const LLSD& data); - void onRemoveOutfit(const LLUUID& outfit_cat_id); - void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id); private: LLOutfitListBase* mOutfitList; }; @@ -236,10 +218,6 @@ public: protected: /*virtual*/ void onUpdateItemsVisibility(); private: - /*virtual*/ void onUploadFoto(); - /*virtual*/ void onSelectPhoto(); - /*virtual*/ void onTakeSnapshot(); - /*virtual*/ void onRemovePhoto(); /*virtual*/ void onChangeSortOrder(); bool hasDefaultImage(); @@ -259,14 +237,21 @@ public: /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + + bool openOutfitsContent(); + void setGallery(LLOutfitGallery* gallery) { mGallery = gallery; } void setDefaultImage(); bool setImageAssetId(LLUUID asset_id); LLUUID getImageAssetId(); void setOutfitName(std::string name); void setOutfitWorn(bool value); void setSelected(bool value); - void setUUID(LLUUID outfit_id) {mUUID = outfit_id;} + void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;} + LLUUID getUUID() const { return mUUID; } std::string getItemName() {return mOutfitName;} bool isDefaultImage() {return mDefaultImage;} @@ -275,6 +260,7 @@ public: void setHidden(bool hidden) {mHidden = hidden;} private: + LLOutfitGallery* mGallery; LLPointer<LLViewerFetchedTexture> mTexturep; LLUUID mUUID; LLUUID mImageAssetId; diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 4171fd8822..5c7792b0df 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -35,7 +35,7 @@ #include "llaccordionctrltab.h" #include "llagentwearables.h" #include "llappearancemgr.h" -#include "llagentbenefits.h" +#include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" @@ -122,9 +122,8 @@ void LLOutfitsList::onOpen(const LLSD& info) { if (!mIsInitialized) { - const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); // Start observing changes in Current Outfit category. - mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this)); + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLOutfitsList::onCOFChanged, this)); } LLOutfitListBase::onOpen(info); @@ -1112,10 +1111,7 @@ LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this)); - registrar.add("Gear.UploadPhoto", boost::bind(&LLOutfitListGearMenuBase::onUploadFoto, this)); - registrar.add("Gear.SelectPhoto", boost::bind(&LLOutfitListGearMenuBase::onSelectPhoto, this)); - registrar.add("Gear.TakeSnapshot", boost::bind(&LLOutfitListGearMenuBase::onTakeSnapshot, this)); - registrar.add("Gear.RemovePhoto", boost::bind(&LLOutfitListGearMenuBase::onRemovePhoto, this)); + registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this)); registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2)); @@ -1232,7 +1228,6 @@ bool LLOutfitListGearMenuBase::onEnable(LLSD::String param) bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) { - getMenu()->getChild<LLUICtrl>("upload_photo")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); const LLUUID& selected_outfit_id = getSelectedOutfitID(); if (selected_outfit_id.isNull()) // no selection or invalid outfit selected { @@ -1251,24 +1246,11 @@ bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) return true; } -void LLOutfitListGearMenuBase::onUploadFoto() +void LLOutfitListGearMenuBase::onThumbnail() { - -} - -void LLOutfitListGearMenuBase::onSelectPhoto() -{ - -} - -void LLOutfitListGearMenuBase::onTakeSnapshot() -{ - -} - -void LLOutfitListGearMenuBase::onRemovePhoto() -{ - + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + LLSD data(selected_outfit_id); + LLFloaterReg::showInstance("change_item_thumbnail", data); } void LLOutfitListGearMenuBase::onChangeSortOrder() @@ -1288,10 +1270,7 @@ void LLOutfitListGearMenu::onUpdateItemsVisibility() if (!mMenu) return; mMenu->setItemVisible("expand", TRUE); mMenu->setItemVisible("collapse", TRUE); - mMenu->setItemVisible("upload_photo", FALSE); - mMenu->setItemVisible("select_photo", FALSE); - mMenu->setItemVisible("take_snapshot", FALSE); - mMenu->setItemVisible("remove_photo", FALSE); + mMenu->setItemVisible("thumbnail", FALSE); // Never visible? mMenu->setItemVisible("sepatator3", FALSE); mMenu->setItemVisible("sort_folders_by_name", FALSE); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index 81be8de94f..66b3165169 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -163,10 +163,7 @@ public: protected: virtual void onUpdateItemsVisibility(); - virtual void onUploadFoto(); - virtual void onSelectPhoto(); - virtual void onTakeSnapshot(); - virtual void onRemovePhoto(); + virtual void onThumbnail(); virtual void onChangeSortOrder(); const LLUUID& getSelectedOutfitID(); diff --git a/indra/newview/llpanellandmarkinfo.cpp b/indra/newview/llpanellandmarkinfo.cpp index 834e664723..cc3c51dd83 100644 --- a/indra/newview/llpanellandmarkinfo.cpp +++ b/indra/newview/llpanellandmarkinfo.cpp @@ -111,9 +111,9 @@ void LLPanelLandmarkInfo::setInfoType(EInfoType type) } // Sets CREATE_LANDMARK infotype and creates landmark at desired folder -void LLPanelLandmarkInfo::setInfoAndCreateLandmark(const LLUUID& fodler_id) +void LLPanelLandmarkInfo::setInfoAndCreateLandmark(const LLUUID& folder_id) { - setInfoType(CREATE_LANDMARK, fodler_id); + setInfoType(CREATE_LANDMARK, folder_id); } void LLPanelLandmarkInfo::setInfoType(EInfoType type, const LLUUID &folder_id) diff --git a/indra/newview/llpanellandmarkinfo.h b/indra/newview/llpanellandmarkinfo.h index 46e2a1935b..8802ce066e 100644 --- a/indra/newview/llpanellandmarkinfo.h +++ b/indra/newview/llpanellandmarkinfo.h @@ -48,7 +48,7 @@ public: /*virtual*/ void setInfoType(EInfoType type); // Sets CREATE_LANDMARK infotype and creates landmark at desired folder - void setInfoAndCreateLandmark(const LLUUID& fodler_id); + void setInfoAndCreateLandmark(const LLUUID& folder_id); /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 8f1e57e44c..49756a4e09 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -1110,11 +1110,6 @@ void LLPanelLogin::onRememberPasswordCheck(void*) std::string grid(LLGridManager::getInstance()->getGridId()); std::string user_id(cred->userID()); - if (!remember_password) - { - gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); - gSecAPIHandler->syncProtectedMap(); - } } } diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index 744d49ff57..30f301027c 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -37,8 +37,8 @@ #include "llfilepicker.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" +#include "llinventorygallery.h" #include "llinventorymodelbackgroundfetch.h" -#include "llinventorypanel.h" #include "llfiltereditor.h" #include "llfloatersidepanelcontainer.h" #include "llfloaterreg.h" @@ -53,12 +53,14 @@ #include "llspinctrl.h" #include "lltoggleablemenu.h" #include "lltooldraganddrop.h" +#include "lltrans.h" #include "llviewermenu.h" #include "llviewertexturelist.h" #include "llsidepanelinventory.h" #include "llfolderview.h" #include "llradiogroup.h" #include "llenvironment.h" +#include "llweb.h" const std::string FILTERS_FILENAME("filters.xml"); @@ -113,7 +115,13 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mMenuGearDefault(NULL), mMenuVisibility(NULL), mMenuAddHandle(), - mNeedUploadCost(true) + mNeedUploadCost(true), + mMenuViewDefault(NULL), + mSingleFolderMode(false), + mForceShowInvLayout(false), + mViewMode(MODE_COMBINATION), + mListViewRootUpdatedConnection(), + mGalleryRootUpdatedConnection() { // Menu Callbacks (non contex menus) mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelMainInventory::doToSelected, this, _2)); @@ -124,7 +132,6 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mCommitCallbackRegistrar.add("Inventory.ShowFilters", boost::bind(&LLPanelMainInventory::toggleFindOptions, this)); mCommitCallbackRegistrar.add("Inventory.ResetFilters", boost::bind(&LLPanelMainInventory::resetFilters, this)); mCommitCallbackRegistrar.add("Inventory.SetSortBy", boost::bind(&LLPanelMainInventory::setSortBy, this, _2)); - mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); mEnableCallbackRegistrar.add("Inventory.EnvironmentEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasSettingsInventory(); }); @@ -192,7 +199,7 @@ BOOL LLPanelMainInventory::postBuild() } // Now load the stored settings from disk, if available. std::string filterSaveName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME)); - LL_INFOS() << "LLPanelMainInventory::init: reading from " << filterSaveName << LL_ENDL; + LL_INFOS("Inventory") << "LLPanelMainInventory::init: reading from " << filterSaveName << LL_ENDL; llifstream file(filterSaveName.c_str()); LLSD savedFilterState; if (file.is_open()) @@ -237,6 +244,34 @@ BOOL LLPanelMainInventory::postBuild() mGearMenuButton = getChild<LLMenuButton>("options_gear_btn"); mVisibilityMenuButton = getChild<LLMenuButton>("options_visibility_btn"); + mViewMenuButton = getChild<LLMenuButton>("view_btn"); + + mBackBtn = getChild<LLButton>("back_btn"); + mForwardBtn = getChild<LLButton>("forward_btn"); + mUpBtn = getChild<LLButton>("up_btn"); + mViewModeBtn = getChild<LLButton>("view_mode_btn"); + mNavigationBtnsPanel = getChild<LLLayoutPanel>("nav_buttons"); + + mDefaultViewPanel = getChild<LLPanel>("default_inventory_panel"); + mCombinationViewPanel = getChild<LLPanel>("combination_view_inventory"); + mCombinationGalleryLayoutPanel = getChild<LLLayoutPanel>("comb_gallery_layout"); + mCombinationListLayoutPanel = getChild<LLLayoutPanel>("comb_inventory_layout"); + mCombinationLayoutStack = getChild<LLLayoutStack>("combination_view_stack"); + + mCombinationInventoryPanel = getChild<LLInventorySingleFolderPanel>("comb_single_folder_inv"); + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + mCombinationInventoryPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onCombinationInventorySelectionChanged, this, _1, _2)); + mListViewRootUpdatedConnection = mCombinationInventoryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, false)); + + mCombinationGalleryPanel = getChild<LLInventoryGallery>("comb_gallery_view_inv"); + mCombinationGalleryPanel->setSortOrder(mCombinationInventoryPanel->getSortOrder()); + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); + comb_gallery_filter.markDefault(); + mGalleryRootUpdatedConnection = mCombinationGalleryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, true)); + mCombinationGalleryPanel->setSelectionChangeCallback(boost::bind(&LLPanelMainInventory::onCombinationGallerySelectionChanged, this, _1)); initListCommandsHandlers(); @@ -308,12 +343,21 @@ LLPanelMainInventory::~LLPanelMainInventory( void ) gInventory.removeObserver(this); delete mSavedFolderState; - auto menu = mMenuAddHandle.get(); - if(menu) - { - menu->die(); - mMenuAddHandle.markDead(); - } + auto menu = mMenuAddHandle.get(); + if(menu) + { + menu->die(); + mMenuAddHandle.markDead(); + } + + if (mListViewRootUpdatedConnection.connected()) + { + mListViewRootUpdatedConnection.disconnect(); + } + if (mGalleryRootUpdatedConnection.connected()) + { + mGalleryRootUpdatedConnection.disconnect(); + } } LLInventoryPanel* LLPanelMainInventory::getAllItemsPanel() @@ -362,6 +406,10 @@ BOOL LLPanelMainInventory::handleKeyHere(KEY key, MASK mask) { startSearch(); } + if(mSingleFolderMode && key == KEY_LEFT) + { + onBackFolderClicked(); + } } return LLPanel::handleKeyHere(key, mask); @@ -381,27 +429,145 @@ void LLPanelMainInventory::closeAllFolders() getPanel()->getRootFolder()->closeAllFolders(); } -void LLPanelMainInventory::newWindow() +S32 get_instance_num() +{ + static S32 instance_num = 0; + instance_num = (instance_num + 1) % S32_MAX; + + return instance_num; +} + +LLFloaterSidePanelContainer* LLPanelMainInventory::newWindow() { - static S32 instance_num = 0; - instance_num = (instance_num + 1) % S32_MAX; + S32 instance_num = get_instance_num(); if (!gAgentCamera.cameraMouselook()) { - LLFloaterReg::showTypedInstance<LLFloaterSidePanelContainer>("inventory", LLSD(instance_num)); + LLFloaterSidePanelContainer* floater = LLFloaterReg::showTypedInstance<LLFloaterSidePanelContainer>("inventory", LLSD(instance_num)); + LLSidepanelInventory* sidepanel_inventory = floater->findChild<LLSidepanelInventory>("main_panel"); + sidepanel_inventory->initInventoryViews(); + return floater; } + return NULL; +} + +//static +void LLPanelMainInventory::newFolderWindow(LLUUID folder_id, LLUUID item_to_select) +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* inventory_container = dynamic_cast<LLFloaterSidePanelContainer*>(*iter++); + if (inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast<LLSidepanelInventory*>(inventory_container->findChild<LLPanel>("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && main_inventory->isSingleFolderMode() + && (main_inventory->getCurrentSFVRoot() == folder_id)) + { + main_inventory->setFocus(true); + if(item_to_select.notNull()) + { + main_inventory->setGallerySelection(item_to_select); + } + return; + } + } + } + } + + S32 instance_num = get_instance_num(); + + LLFloaterSidePanelContainer* inventory_container = LLFloaterReg::showTypedInstance<LLFloaterSidePanelContainer>("inventory", LLSD(instance_num)); + if(inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast<LLSidepanelInventory*>(inventory_container->findChild<LLPanel>("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + main_inventory->initSingleFolderRoot(folder_id); + main_inventory->toggleViewMode(); + if(folder_id.notNull()) + { + if(item_to_select.notNull()) + { + main_inventory->setGallerySelection(item_to_select, true); + } + } + } + } + } } void LLPanelMainInventory::doCreate(const LLSD& userdata) { reset_inventory_filter(); - menu_create_inventory_item(getPanel(), NULL, userdata); + if(mSingleFolderMode) + { + if(isListViewMode() || isCombinationViewMode()) + { + LLFolderViewItem* current_folder = getActivePanel()->getRootFolder(); + if (current_folder) + { + if(isCombinationViewMode()) + { + mForceShowInvLayout = true; + } + + LLHandle<LLPanel> handle = getHandle(); + std::function<void(const LLUUID&)> callback_created = [handle](const LLUUID& new_id) + { + gInventory.notifyObservers(); // not really needed, should have been already done + LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); + if (new_id.notNull() && panel) + { + // might need to refresh visibility, delay rename + panel->mCombInvUUIDNeedsRename = new_id; + + if (panel->isCombinationViewMode()) + { + panel->mForceShowInvLayout = true; + } + + LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; + } + }; + menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); + } + } + else + { + LLHandle<LLPanel> handle = getHandle(); + std::function<void(const LLUUID&)> callback_created = [handle](const LLUUID &new_id) + { + gInventory.notifyObservers(); // not really needed, should have been already done + if (new_id.notNull()) + { + LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); + if (panel) + { + panel->setGallerySelection(new_id); + LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; + } + } + }; + menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); + } + } + else + { + menu_create_inventory_item(getPanel(), NULL, userdata); + } } void LLPanelMainInventory::resetFilters() { LLFloaterInventoryFinder *finder = getFinder(); - getActivePanel()->getFilter().resetDefault(); + getCurrentFilter().resetDefault(); if (finder) { finder->updateElementsFromFilter(); @@ -422,6 +588,17 @@ void LLPanelMainInventory::resetAllItemsFilters() setFilterTextFromFilter(); } +void LLPanelMainInventory::findLinks(const LLUUID& item_id, const std::string& item_name) +{ + mFilterSubString = item_name; + + LLInventoryFilter &filter = mActivePanel->getFilter(); + filter.setFindAllLinksMode(item_name, item_id); + + mFilterEditor->setText(item_name); + mFilterEditor->setFocus(TRUE); +} + void LLPanelMainInventory::setSortBy(const LLSD& userdata) { U32 sort_order_mask = getActivePanel()->getSortOrder(); @@ -456,6 +633,10 @@ void LLPanelMainInventory::setSortBy(const LLSD& userdata) sort_order_mask |= LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; } } + if(mSingleFolderMode && !isListViewMode()) + { + mCombinationGalleryPanel->setSortOrder(sort_order_mask, true); + } getActivePanel()->setSortOrder(sort_order_mask); if (isRecentItemsPanelSelected()) @@ -473,25 +654,56 @@ void LLPanelMainInventory::onSelectSearchType() std::string new_type = mSearchTypeCombo->getValue(); if (new_type == "search_by_name") { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); + setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); } if (new_type == "search_by_creator") { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_CREATOR); + setSearchType(LLInventoryFilter::SEARCHTYPE_CREATOR); } if (new_type == "search_by_description") { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_DESCRIPTION); + setSearchType(LLInventoryFilter::SEARCHTYPE_DESCRIPTION); } if (new_type == "search_by_UUID") { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_UUID); + setSearchType(LLInventoryFilter::SEARCHTYPE_UUID); } } +void LLPanelMainInventory::setSearchType(LLInventoryFilter::ESearchType type) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mCombinationGalleryPanel->setSearchType(type); + } + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationInventoryPanel->setSearchType(type); + mCombinationGalleryPanel->setSearchType(type); + } + else + { + getActivePanel()->setSearchType(type); + } +} + void LLPanelMainInventory::updateSearchTypeCombo() { - LLInventoryFilter::ESearchType search_type = getActivePanel()->getSearchType(); + LLInventoryFilter::ESearchType search_type(LLInventoryFilter::SEARCHTYPE_NAME); + + if(mSingleFolderMode && isGalleryViewMode()) + { + search_type = mCombinationGalleryPanel->getSearchType(); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + search_type = mCombinationGalleryPanel->getSearchType(); + } + else + { + search_type = getActivePanel()->getSearchType(); + } + switch(search_type) { case LLInventoryFilter::SEARCHTYPE_CREATOR: @@ -537,7 +749,7 @@ void LLPanelMainInventory::onClearSearch() } // re-open folders that were initially open in case filter was active - if (mActivePanel && (mFilterSubString.size() || initially_active)) + if (mActivePanel && (mFilterSubString.size() || initially_active) && !mSingleFolderMode) { mSavedFolderState->setApply(TRUE); mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); @@ -547,7 +759,7 @@ void LLPanelMainInventory::onClearSearch() } mFilterSubString = ""; - LLSidepanelInventory * sidepanel_inventory = LLFloaterSidePanelContainer::getPanel<LLSidepanelInventory>("inventory"); + LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); if (sidepanel_inventory) { LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild<LLPanelMarketplaceInbox>("marketplace_inbox"); @@ -560,16 +772,32 @@ void LLPanelMainInventory::onClearSearch() void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) { + if(mSingleFolderMode && isGalleryViewMode()) + { + mFilterSubString = search_string; + mCombinationGalleryPanel->setFilterSubString(mFilterSubString); + return; + } + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationGalleryPanel->setFilterSubString(search_string); + } + if (search_string == "") { onClearSearch(); } + if (!mActivePanel) { return; } - LLInventoryModelBackgroundFetch::instance().start(); + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } mFilterSubString = search_string; if (mActivePanel->getFilterSubString().empty() && mFilterSubString.empty()) @@ -588,7 +816,7 @@ void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) // set new filter string setFilterSubString(mFilterSubString); - LLSidepanelInventory * sidepanel_inventory = LLFloaterSidePanelContainer::getPanel<LLSidepanelInventory>("inventory"); + LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); if (sidepanel_inventory) { LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild<LLPanelMarketplaceInbox>("marketplace_inbox"); @@ -643,7 +871,7 @@ void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) void LLPanelMainInventory::onFilterSelected() { // Find my index - mActivePanel = (LLInventoryPanel*)getChild<LLTabContainer>("inventory filter tabs")->getCurrentPanel(); + setActivePanel(); if (!mActivePanel) { @@ -656,15 +884,19 @@ void LLPanelMainInventory::onFilterSelected() } updateSearchTypeCombo(); setFilterSubString(mFilterSubString); - LLInventoryFilter& filter = mActivePanel->getFilter(); + LLInventoryFilter& filter = getCurrentFilter(); LLFloaterInventoryFinder *finder = getFinder(); if (finder) { finder->changeFilter(&filter); + if (mSingleFolderMode) + { + finder->setTitle(getLocalizedRootName()); + } } - if (filter.isActive()) + if (filter.isActive() && !LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) { - // If our filter is active we may be the first thing requiring a fetch so we better start it here. + llassert(false); // this should have been done on startup LLInventoryModelBackgroundFetch::instance().start(); } setFilterTextFromFilter(); @@ -736,6 +968,7 @@ void LLPanelMainInventory::draw() } LLPanel::draw(); updateItemcountText(); + updateCombinationVisibility(); } void LLPanelMainInventory::updateItemcountText() @@ -775,6 +1008,21 @@ void LLPanelMainInventory::updateItemcountText() { text = getString("ItemcountUnknown", string_args); } + + if (mSingleFolderMode) + { + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + + gInventory.getDirectDescendentsOf(getCurrentSFVRoot(), cats, items); + + if (items && cats) + { + string_args["[ITEM_COUNT]"] = llformat("%d", items->size()); + string_args["[CATEGORY_COUNT]"] = llformat("%d", cats->size()); + text = getString("ItemcountCompleted", string_args); + } + } mCounterCtrl->setValue(text); mCounterCtrl->setToolTip(text); @@ -794,7 +1042,7 @@ void LLPanelMainInventory::onFocusReceived() void LLPanelMainInventory::setFilterTextFromFilter() { - mFilterText = mActivePanel->getFilter().getFilterText(); + mFilterText = getCurrentFilter().getFilterText(); } void LLPanelMainInventory::toggleFindOptions() @@ -809,8 +1057,17 @@ void LLPanelMainInventory::toggleFindOptions() LLFloater* parent_floater = gFloaterView->getParentFloater(this); if (parent_floater) parent_floater->addDependentFloater(mFinderHandle); - // start background fetch of folders - LLInventoryModelBackgroundFetch::instance().start(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + + if (mSingleFolderMode) + { + finder->setTitle(getLocalizedRootName()); + } } else { @@ -1043,10 +1300,27 @@ void LLFloaterInventoryFinder::draw() filter &= ~(0x1 << LLInventoryType::IT_CATEGORY); } - // update the panel, panel will update the filter - mPanelMainInventory->getPanel()->setShowFolderState(getCheckShowEmpty() ? - LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mPanelMainInventory->getPanel()->setFilterTypes(filter); + + bool is_sf_mode = mPanelMainInventory->isSingleFolderMode(); + if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); + } + else + { + if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); + } + // update the panel, panel will update the filter + mPanelMainInventory->getPanel()->setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->getPanel()->setFilterTypes(filter); + } if (getCheckSinceLogoff()) { @@ -1068,10 +1342,26 @@ void LLFloaterInventoryFinder::draw() } hours += days * 24; - mPanelMainInventory->getPanel()->setHoursAgo(hours); - mPanelMainInventory->getPanel()->setSinceLogoff(getCheckSinceLogoff()); + mPanelMainInventory->setFilterTextFromFilter(); - mPanelMainInventory->getPanel()->setDateSearchDirection(getDateSearchDirection()); + if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); + } + else + { + if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); + } + mPanelMainInventory->getPanel()->setHoursAgo(hours); + mPanelMainInventory->getPanel()->setSinceLogoff(getCheckSinceLogoff()); + mPanelMainInventory->getPanel()->setDateSearchDirection(getDateSearchDirection()); + } LLPanel::draw(); } @@ -1083,15 +1373,15 @@ void LLFloaterInventoryFinder::onCreatorSelfFilterCommit() if(show_creator_self && show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); } else if(show_creator_self) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); } else if(!show_creator_self || !show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); mCreatorOthers->set(TRUE); } } @@ -1103,15 +1393,15 @@ void LLFloaterInventoryFinder::onCreatorOtherFilterCommit() if(show_creator_self && show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); } else if(show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); } else if(!show_creator_other || !show_creator_self) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); mCreatorSelf->set(TRUE); } } @@ -1182,26 +1472,25 @@ void LLFloaterInventoryFinder::selectNoTypes(void* user_data) void LLPanelMainInventory::initListCommandsHandlers() { - childSetAction("trash_btn", boost::bind(&LLPanelMainInventory::onTrashButtonClick, this)); childSetAction("add_btn", boost::bind(&LLPanelMainInventory::onAddButtonClick, this)); - - mTrashButton = getChild<LLDragAndDropButton>("trash_btn"); - mTrashButton->setDragAndDropHandler(boost::bind(&LLPanelMainInventory::handleDragAndDropToTrash, this - , _4 // BOOL drop - , _5 // EDragAndDropType cargo_type - , _7 // EAcceptance* accept - )); + childSetAction("view_mode_btn", boost::bind(&LLPanelMainInventory::onViewModeClick, this)); + childSetAction("up_btn", boost::bind(&LLPanelMainInventory::onUpFolderClicked, this)); + childSetAction("back_btn", boost::bind(&LLPanelMainInventory::onBackFolderClicked, this)); + childSetAction("forward_btn", boost::bind(&LLPanelMainInventory::onForwardFolderClicked, this)); mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Visible", boost::bind(&LLPanelMainInventory::isActionVisible, this, _2)); mMenuGearDefault = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_inventory_gear_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mGearMenuButton->setMenu(mMenuGearDefault, LLMenuButton::MP_TOP_LEFT, true); + mGearMenuButton->setMenu(mMenuGearDefault, LLMenuButton::MP_BOTTOM_LEFT, true); + mMenuViewDefault = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_inventory_view_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mViewMenuButton->setMenu(mMenuViewDefault, LLMenuButton::MP_BOTTOM_LEFT, true); LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_inventory_add.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); mMenuAddHandle = menu->getHandle(); mMenuVisibility = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_inventory_search_visibility.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mVisibilityMenuButton->setMenu(mMenuVisibility, LLMenuButton::MP_BOTTOM_LEFT, true); + mVisibilityMenuButton->setMenu(mMenuVisibility, LLMenuButton::MP_BOTTOM_LEFT, true); // Update the trash button when selected item(s) get worn or taken off. LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLPanelMainInventory::updateListCommands, this)); @@ -1209,9 +1498,6 @@ void LLPanelMainInventory::initListCommandsHandlers() void LLPanelMainInventory::updateListCommands() { - bool trash_enabled = isActionEnabled("delete"); - - mTrashButton->setEnabled(trash_enabled); } void LLPanelMainInventory::onAddButtonClick() @@ -1222,7 +1508,7 @@ void LLPanelMainInventory::onAddButtonClick() LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); if (menu) { - menu->getChild<LLMenuItemGL>("New Folder")->setEnabled(!isRecentItemsPanelSelected()); + disableAddIfNeeded(); setUploadCostIfNeeded(); @@ -1230,6 +1516,215 @@ void LLPanelMainInventory::onAddButtonClick() } } +void LLPanelMainInventory::setActivePanel() +{ + // Todo: should cover gallery mode in some way + if(mSingleFolderMode && isListViewMode()) + { + mActivePanel = getChild<LLInventoryPanel>("comb_single_folder_inv"); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + mActivePanel = getChild<LLInventoryPanel>("comb_single_folder_inv"); + } + else + { + mActivePanel = (LLInventoryPanel*)getChild<LLTabContainer>("inventory filter tabs")->getCurrentPanel(); + } + mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); +} + +void LLPanelMainInventory::initSingleFolderRoot(const LLUUID& start_folder_id) +{ + mCombinationInventoryPanel->initFolderRoot(start_folder_id); +} + +void LLPanelMainInventory::initInventoryViews() +{ + LLInventoryPanel* all_item = getChild<LLInventoryPanel>(ALL_ITEMS); + all_item->initializeViewBuilding(); + LLInventoryPanel* recent_item = getChild<LLInventoryPanel>(RECENT_ITEMS); + recent_item->initializeViewBuilding(); + LLInventoryPanel* worn_item = getChild<LLInventoryPanel>(WORN_ITEMS); + worn_item->initializeViewBuilding(); +} + +void LLPanelMainInventory::toggleViewMode() +{ + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); + } + + mSingleFolderMode = !mSingleFolderMode; + mReshapeInvLayout = true; + + if (mCombinationGalleryPanel->getRootFolder().isNull()) + { + mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); + mCombinationGalleryPanel->updateRootFolder(); + } + + updatePanelVisibility(); + setActivePanel(); + updateTitle(); + onFilterSelected(); + + LLSidepanelInventory* sidepanel_inventory = getParentSidepanelInventory(); + if (sidepanel_inventory) + { + if(mSingleFolderMode) + { + sidepanel_inventory->hideInbox(); + } + else + { + sidepanel_inventory->toggleInbox(); + } + } +} + +void LLPanelMainInventory::onViewModeClick() +{ + LLUUID selected_folder; + LLUUID new_root_folder; + if(mSingleFolderMode) + { + selected_folder = getCurrentSFVRoot(); + } + else + { + LLFolderView* root = getActivePanel()->getRootFolder(); + std::set<LLFolderViewItem*> selection_set = root->getSelectionList(); + if (selection_set.size() == 1) + { + LLFolderViewItem* current_item = *selection_set.begin(); + if (current_item) + { + const LLUUID& id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + if(gInventory.getCategory(id) != NULL) + { + new_root_folder = id; + } + else + { + const LLViewerInventoryItem* selected_item = gInventory.getItem(id); + if (selected_item && selected_item->getParentUUID().notNull()) + { + new_root_folder = selected_item->getParentUUID(); + selected_folder = id; + } + } + } + } + mCombinationInventoryPanel->initFolderRoot(new_root_folder); + } + + toggleViewMode(); + + if (mSingleFolderMode && new_root_folder.notNull()) + { + setSingleFolderViewRoot(new_root_folder, true); + if(selected_folder.notNull() && isListViewMode()) + { + getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); + } + } + else + { + if(selected_folder.notNull()) + { + selectAllItemsPanel(); + getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); + } + } +} + +void LLPanelMainInventory::onUpFolderClicked() +{ + const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); + if (cat) + { + if (cat->getParentUUID().notNull()) + { + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(cat->getParentUUID()); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); + } + } + } +} + +void LLPanelMainInventory::onBackFolderClicked() +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->onBackwardFolder(); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->onBackwardFolder(); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->onBackwardFolder(); + } +} + +void LLPanelMainInventory::onForwardFolderClicked() +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->onForwardFolder(); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->onForwardFolder(); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->onForwardFolder(); + } +} + +void LLPanelMainInventory::setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history) +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(folder_id); + if(clear_nav_history) + { + mCombinationInventoryPanel->clearNavigationHistory(); + } + } + else if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(folder_id); + if(clear_nav_history) + { + mCombinationGalleryPanel->clearNavigationHistory(); + } + } + else if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(folder_id); + } + updateNavButtons(); +} + +LLUUID LLPanelMainInventory::getSingleFolderViewRoot() +{ + return mCombinationInventoryPanel->getSingleFolderRoot(); +} + void LLPanelMainInventory::showActionMenu(LLMenuGL* menu, std::string spawning_view_name) { if (menu) @@ -1239,17 +1734,11 @@ void LLPanelMainInventory::showActionMenu(LLMenuGL* menu, std::string spawning_v LLView* spawning_view = getChild<LLView> (spawning_view_name); S32 menu_x, menu_y; //show menu in co-ordinates of panel - spawning_view->localPointToOtherView(0, spawning_view->getRect().getHeight(), &menu_x, &menu_y, this); - menu_y += menu->getRect().getHeight(); + spawning_view->localPointToOtherView(0, 0, &menu_x, &menu_y, this); LLMenuGL::showPopup(this, menu, menu_x, menu_y); } } -void LLPanelMainInventory::onTrashButtonClick() -{ - onClipboardAction("delete"); -} - void LLPanelMainInventory::onClipboardAction(const LLSD& userdata) { std::string command_name = userdata.asString(); @@ -1258,13 +1747,22 @@ void LLPanelMainInventory::onClipboardAction(const LLSD& userdata) void LLPanelMainInventory::saveTexture(const LLSD& userdata) { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - - const LLUUID& item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + if (item_id.isNull()) return; + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + } + LLPreviewTexture* preview_texture = LLFloaterReg::showTypedInstance<LLPreviewTexture>("preview_texture", LLSD(item_id), TAKE_FOCUS_YES); if (preview_texture) { @@ -1278,6 +1776,7 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata) return; const std::string command_name = userdata.asString(); + if (command_name == "new_window") { newWindow(); @@ -1347,35 +1846,69 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata) } if (command_name == "find_original") { + if(mSingleFolderMode && isGalleryViewMode()) + { + LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); + if (obj && obj->getIsLinkType()) + { + show_item_original(obj->getUUID()); + } + } + else + { LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (!current_item) { return; } static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->performAction(getActivePanel()->getModel(), "goto"); + } } if (command_name == "find_links") { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - const LLUUID& item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); - const std::string &item_name = current_item->getViewModelItem()->getName(); - mFilterSubString = item_name; - - LLInventoryFilter &filter = mActivePanel->getFilter(); - filter.setFindAllLinksMode(item_name, item_id); - - mFilterEditor->setText(item_name); - mFilterEditor->setFocus(TRUE); + if(mSingleFolderMode && isGalleryViewMode()) + { + LLFloaterSidePanelContainer* inventory_container = newWindow(); + if (inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast<LLSidepanelInventory*>(inventory_container->findChild<LLPanel>("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); + if (obj) + { + main_inventory->findLinks(obj->getUUID(), obj->getName()); + } + } + } + } + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + const LLUUID& item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + const std::string &item_name = current_item->getViewModelItem()->getName(); + findLinks(item_id, item_name); + } } if (command_name == "replace_links") { - LLSD params; + LLSD params; + if(mSingleFolderMode && isGalleryViewMode()) + { + params = LLSD(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else + { LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (current_item) { @@ -1390,23 +1923,72 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata) } } } + } LLFloaterReg::showInstance("linkreplace", params); } + if (command_name == "close_inv_windows") + { + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* iv = dynamic_cast<LLFloaterSidePanelContainer*>(*iter++); + if (iv) + { + iv->closeFloater(); + } + } + LLFloaterReg::hideInstance("inventory_settings"); + } + + if (command_name == "toggle_search_outfits") + { + getCurrentFilter().toggleSearchVisibilityOutfits(); + } + if (command_name == "toggle_search_trash") { - mActivePanel->getFilter().toggleSearchVisibilityTrash(); + getCurrentFilter().toggleSearchVisibilityTrash(); } if (command_name == "toggle_search_library") { - mActivePanel->getFilter().toggleSearchVisibilityLibrary(); + getCurrentFilter().toggleSearchVisibilityLibrary(); } if (command_name == "include_links") { - mActivePanel->getFilter().toggleSearchVisibilityLinks(); - } + getCurrentFilter().toggleSearchVisibilityLinks(); + } + + if (command_name == "share") + { + if(mSingleFolderMode && isGalleryViewMode()) + { + std::set<LLUUID> uuids{ mCombinationGalleryPanel->getFirstSelectedItemID()}; + LLAvatarActions::shareWithAvatars(uuids, gFloaterView->getParentFloater(this)); + } + else + { + LLAvatarActions::shareWithAvatars(this); + } + } + if (command_name == "shop") + { + LLWeb::loadURL(gSavedSettings.getString("MarketplaceURL")); + } + if (command_name == "list_view") + { + setViewMode(MODE_LIST); + } + if (command_name == "gallery_view") + { + setViewMode(MODE_GALLERY); + } + if (command_name == "combination_view") + { + setViewMode(MODE_COMBINATION); + } } void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility ) @@ -1424,17 +2006,26 @@ void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility ) bool LLPanelMainInventory::isSaveTextureEnabled(const LLSD& userdata) { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (current_item) - { - LLViewerInventoryItem *inv_item = dynamic_cast<LLViewerInventoryItem*>(static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getInventoryObject()); + LLViewerInventoryItem *inv_item = NULL; + if(mSingleFolderMode && isGalleryViewMode()) + { + inv_item = gInventory.getItem(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (current_item) + { + inv_item = dynamic_cast<LLViewerInventoryItem*>(static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getInventoryObject()); + } + } if(inv_item) { bool can_save = inv_item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); - LLInventoryType::EType curr_type = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getInventoryType(); + LLInventoryType::EType curr_type = inv_item->getInventoryType(); return can_save && (curr_type == LLInventoryType::IT_TEXTURE || curr_type == LLInventoryType::IT_SNAPSHOT); } - } + return false; } @@ -1465,9 +2056,16 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) } if (command_name == "find_original") { + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + } + else{ LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (!current_item) return FALSE; - const LLUUID& item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + } const LLViewerInventoryItem *item = gInventory.getItem(item_id); if (item && item->getIsLinkType() && !item->getIsBrokenLink()) { @@ -1478,12 +2076,19 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) if (command_name == "find_links") { + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + } + else{ LLFolderView* root = getActivePanel()->getRootFolder(); std::set<LLFolderViewItem*> selection_set = root->getSelectionList(); if (selection_set.size() != 1) return FALSE; LLFolderViewItem* current_item = root->getCurSelectedItem(); if (!current_item) return FALSE; - const LLUUID& item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + item_id = static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->getUUID(); + } const LLInventoryObject *obj = gInventory.getObject(item_id); if (obj && !obj->getIsLinkType() && LLAssetType::lookupCanLink(obj->getType())) { @@ -1507,10 +2112,16 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) if (command_name == "share") { + if(mSingleFolderMode && isGalleryViewMode()) + { + return can_share_item(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else{ LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (!current_item) return FALSE; LLSidepanelInventory* parent = LLFloaterSidePanelContainer::getPanel<LLSidepanelInventory>("inventory"); return parent ? parent->canShare() : FALSE; + } } if (command_name == "empty_trash") { @@ -1528,9 +2139,24 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) return TRUE; } +bool LLPanelMainInventory::isActionVisible(const LLSD& userdata) +{ + const std::string param_str = userdata.asString(); + if (param_str == "single_folder_view") + { + return mSingleFolderMode; + } + if (param_str == "multi_folder_view") + { + return !mSingleFolderMode; + } + + return true; +} + BOOL LLPanelMainInventory::isActionChecked(const LLSD& userdata) { - U32 sort_order_mask = getActivePanel()->getSortOrder(); + U32 sort_order_mask = (mSingleFolderMode && isGalleryViewMode()) ? mCombinationGalleryPanel->getSortOrder() : getActivePanel()->getSortOrder(); const std::string command_name = userdata.asString(); if (command_name == "sort_by_name") { @@ -1552,36 +2178,40 @@ BOOL LLPanelMainInventory::isActionChecked(const LLSD& userdata) return sort_order_mask & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; } + if (command_name == "toggle_search_outfits") + { + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_OUTFITS) != 0; + } + if (command_name == "toggle_search_trash") { - return (mActivePanel->getFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_TRASH) != 0; + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_TRASH) != 0; } if (command_name == "toggle_search_library") { - return (mActivePanel->getFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LIBRARY) != 0; + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LIBRARY) != 0; } if (command_name == "include_links") { - return (mActivePanel->getFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) != 0; + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) != 0; } - return FALSE; -} - -bool LLPanelMainInventory::handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept) -{ - *accept = ACCEPT_NO; - - const bool is_enabled = isActionEnabled("delete"); - if (is_enabled) *accept = ACCEPT_YES_MULTI; + if (command_name == "list_view") + { + return isListViewMode(); + } + if (command_name == "gallery_view") + { + return isGalleryViewMode(); + } + if (command_name == "combination_view") + { + return isCombinationViewMode(); + } - if (is_enabled && drop) - { - onClipboardAction("delete"); - } - return true; + return FALSE; } void LLPanelMainInventory::setUploadCostIfNeeded() @@ -1599,10 +2229,410 @@ void LLPanelMainInventory::setUploadCostIfNeeded() } } +bool is_add_allowed(LLUUID folder_id) +{ + if(!gInventory.isObjectDescendentOf(folder_id, gInventory.getRootFolderID())) + { + return false; + } + + std::vector<LLFolderType::EType> not_allowed_types; + not_allowed_types.push_back(LLFolderType::FT_LOST_AND_FOUND); + not_allowed_types.push_back(LLFolderType::FT_FAVORITE); + not_allowed_types.push_back(LLFolderType::FT_MARKETPLACE_LISTINGS); + not_allowed_types.push_back(LLFolderType::FT_TRASH); + not_allowed_types.push_back(LLFolderType::FT_CURRENT_OUTFIT); + not_allowed_types.push_back(LLFolderType::FT_INBOX); + + for (std::vector<LLFolderType::EType>::const_iterator it = not_allowed_types.begin(); + it != not_allowed_types.end(); ++it) + { + if(gInventory.isObjectDescendentOf(folder_id, gInventory.findCategoryUUIDForType(*it))) + { + return false; + } + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + return false; + } + return true; +} + +void LLPanelMainInventory::disableAddIfNeeded() +{ + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if (menu) + { + bool enable = !mSingleFolderMode || is_add_allowed(getCurrentSFVRoot()); + + menu->getChild<LLMenuItemGL>("New Folder")->setEnabled(enable && !isRecentItemsPanelSelected()); + menu->getChild<LLMenuItemGL>("New Script")->setEnabled(enable); + menu->getChild<LLMenuItemGL>("New Note")->setEnabled(enable); + menu->getChild<LLMenuItemGL>("New Gesture")->setEnabled(enable); + menu->setItemEnabled("New Clothes", enable); + menu->setItemEnabled("New Body Parts", enable); + menu->setItemEnabled("New Settings", enable); + } +} + bool LLPanelMainInventory::hasSettingsInventory() { return LLEnvironment::instance().isInventoryEnabled(); } +void LLPanelMainInventory::updateTitle() +{ + LLFloater* inventory_floater = gFloaterView->getParentFloater(this); + if(inventory_floater) + { + if(mSingleFolderMode) + { + inventory_floater->setTitle(getLocalizedRootName()); + LLFloaterInventoryFinder *finder = getFinder(); + if (finder) + { + finder->setTitle(getLocalizedRootName()); + } + } + else + { + inventory_floater->setTitle(getString("inventory_title")); + } + } + updateNavButtons(); +} + +void LLPanelMainInventory::onCombinationRootChanged(bool gallery_clicked) +{ + if(gallery_clicked) + { + mCombinationInventoryPanel->changeFolderRoot(mCombinationGalleryPanel->getRootFolder()); + } + else + { + mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); + } + mForceShowInvLayout = false; + updateTitle(); + mReshapeInvLayout = true; +} + +void LLPanelMainInventory::onCombinationGallerySelectionChanged(const LLUUID& category_id) +{ +} + +void LLPanelMainInventory::onCombinationInventorySelectionChanged(const std::deque<LLFolderViewItem*>& items, BOOL user_action) +{ + onSelectionChange(mCombinationInventoryPanel, items, user_action); +} + +void LLPanelMainInventory::updatePanelVisibility() +{ + mDefaultViewPanel->setVisible(!mSingleFolderMode); + mCombinationViewPanel->setVisible(mSingleFolderMode); + mNavigationBtnsPanel->setVisible(mSingleFolderMode); + mViewModeBtn->setImageOverlay(mSingleFolderMode ? getString("default_mode_btn") : getString("single_folder_mode_btn")); + mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); + if (mSingleFolderMode) + { + if (isCombinationViewMode()) + { + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); + comb_gallery_filter.markDefault(); + + // visibility will be controled by updateCombinationVisibility() + mCombinationGalleryLayoutPanel->setVisible(true); + mCombinationGalleryPanel->setVisible(true); + mCombinationListLayoutPanel->setVisible(true); + } + else + { + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); + comb_gallery_filter.markDefault(); + + mCombinationLayoutStack->setPanelSpacing(0); + mCombinationGalleryLayoutPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); + mCombinationGalleryPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); // to prevent or process updates + mCombinationListLayoutPanel->setVisible(mSingleFolderMode && isListViewMode()); + } + } + else + { + mCombinationGalleryLayoutPanel->setVisible(false); + mCombinationGalleryPanel->setVisible(false); // to prevent updates + mCombinationListLayoutPanel->setVisible(false); + } +} + +void LLPanelMainInventory::updateCombinationVisibility() +{ + if(mSingleFolderMode && isCombinationViewMode()) + { + bool is_gallery_empty = !mCombinationGalleryPanel->hasVisibleItems(); + bool show_inv_pane = mCombinationInventoryPanel->hasVisibleItems() || is_gallery_empty || mForceShowInvLayout; + + const S32 DRAG_HANDLE_PADDING = 12; // for drag handle to not overlap gallery when both inventories are visible + mCombinationLayoutStack->setPanelSpacing(show_inv_pane ? DRAG_HANDLE_PADDING : 0); + + mCombinationGalleryLayoutPanel->setVisible(!is_gallery_empty); + mCombinationListLayoutPanel->setVisible(show_inv_pane); + mCombinationInventoryPanel->getRootFolder()->setForceArrange(!show_inv_pane); + if(mCombinationInventoryPanel->hasVisibleItems()) + { + mForceShowInvLayout = false; + } + if(is_gallery_empty) + { + mCombinationGalleryPanel->handleModifiedFilter(); + } + + getActivePanel()->getRootFolder(); + + if (mReshapeInvLayout + && show_inv_pane + && (mCombinationGalleryPanel->hasVisibleItems() || mCombinationGalleryPanel->areViewsInitialized()) + && mCombinationInventoryPanel->areViewsInitialized()) + { + mReshapeInvLayout = false; + + // force drop previous shape (because panel doesn't decrease shape properly) + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom; // min height is at 100, so it should snap to be bigger + mCombinationListLayoutPanel->setShape(list_latout, false); + + LLRect inv_inner_rect = mCombinationInventoryPanel->getScrollableContainer()->getScrolledViewRect(); + S32 inv_height = inv_inner_rect.getHeight() + + (mCombinationInventoryPanel->getScrollableContainer()->getBorderWidth() * 2) + + mCombinationInventoryPanel->getScrollableContainer()->getSize(); + LLRect inner_galery_rect = mCombinationGalleryPanel->getScrollableContainer()->getScrolledViewRect(); + S32 gallery_height = inner_galery_rect.getHeight() + + (mCombinationGalleryPanel->getScrollableContainer()->getBorderWidth() * 2) + + mCombinationGalleryPanel->getScrollableContainer()->getSize(); + LLRect layout_rect = mCombinationViewPanel->getRect(); + + // by default make it take 1/3 of the panel + S32 list_default_height = layout_rect.getHeight() / 3; + // Don't set height from gallery_default_height - needs to account for a resizer in such case + S32 gallery_default_height = layout_rect.getHeight() - list_default_height; + + if (inv_height > list_default_height + && gallery_height < gallery_default_height) + { + LLRect gallery_latout = mCombinationGalleryLayoutPanel->getRect(); + gallery_latout.mTop = gallery_latout.mBottom + gallery_height; + mCombinationGalleryLayoutPanel->setShape(gallery_latout, true /*tell stack to account for new shape*/); + } + else if (inv_height < list_default_height + && gallery_height > gallery_default_height) + { + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom + inv_height; + mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); + } + else + { + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom + list_default_height; + mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); + } + } + } + + if (mSingleFolderMode + && !isGalleryViewMode() + && mCombInvUUIDNeedsRename.notNull() + && mCombinationInventoryPanel->areViewsInitialized()) + { + mCombinationInventoryPanel->setSelectionByID(mCombInvUUIDNeedsRename, TRUE); + mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); + mCombinationInventoryPanel->getRootFolder()->setNeedsAutoRename(TRUE); + mCombInvUUIDNeedsRename.setNull(); + } +} + +void LLPanelMainInventory::updateNavButtons() +{ + if(isListViewMode()) + { + mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); + } + if(isGalleryViewMode()) + { + mBackBtn->setEnabled(mCombinationGalleryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationGalleryPanel->isForwardAvailable()); + } + if(isCombinationViewMode()) + { + mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); + } + + const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); + bool up_enabled = (cat && cat->getParentUUID().notNull()); + mUpBtn->setEnabled(up_enabled); +} + +LLSidepanelInventory* LLPanelMainInventory::getParentSidepanelInventory() +{ + LLFloaterSidePanelContainer* inventory_container = dynamic_cast<LLFloaterSidePanelContainer*>(gFloaterView->getParentFloater(this)); + if(inventory_container) + { + return dynamic_cast<LLSidepanelInventory*>(inventory_container->findChild<LLPanel>("main_panel", true)); + } + return NULL; +} + +void LLPanelMainInventory::setViewMode(EViewModeType mode) +{ + if(mode != mViewMode) + { + std::list<LLUUID> forward_history; + std::list<LLUUID> backward_history; + U32 sort_order = 0; + switch(mViewMode) + { + case MODE_LIST: + forward_history = mCombinationInventoryPanel->getNavForwardList(); + backward_history = mCombinationInventoryPanel->getNavBackwardList(); + sort_order = mCombinationInventoryPanel->getSortOrder(); + break; + case MODE_GALLERY: + forward_history = mCombinationGalleryPanel->getNavForwardList(); + backward_history = mCombinationGalleryPanel->getNavBackwardList(); + sort_order = mCombinationGalleryPanel->getSortOrder(); + break; + case MODE_COMBINATION: + forward_history = mCombinationInventoryPanel->getNavForwardList(); + backward_history = mCombinationInventoryPanel->getNavBackwardList(); + mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); + sort_order = mCombinationInventoryPanel->getSortOrder(); + break; + } + + LLUUID cur_root = getCurrentSFVRoot(); + mViewMode = mode; + + updatePanelVisibility(); + + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cur_root); + mCombinationInventoryPanel->setNavForwardList(forward_history); + mCombinationInventoryPanel->setNavBackwardList(backward_history); + mCombinationInventoryPanel->setSortOrder(sort_order); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(cur_root); + mCombinationGalleryPanel->setNavForwardList(forward_history); + mCombinationGalleryPanel->setNavBackwardList(backward_history); + mCombinationGalleryPanel->setSortOrder(sort_order, true); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cur_root); + mCombinationGalleryPanel->setRootFolder(cur_root); + mCombinationInventoryPanel->setNavForwardList(forward_history); + mCombinationInventoryPanel->setNavBackwardList(backward_history); + mCombinationGalleryPanel->setNavForwardList(forward_history); + mCombinationGalleryPanel->setNavBackwardList(backward_history); + mCombinationInventoryPanel->setSortOrder(sort_order); + mCombinationGalleryPanel->setSortOrder(sort_order, true); + } + + updateNavButtons(); + + onFilterSelected(); + if((isListViewMode() && (mActivePanel->getFilterSubString() != mFilterSubString)) || + (isGalleryViewMode() && (mCombinationGalleryPanel->getFilterSubString() != mFilterSubString))) + { + onFilterEdit(mFilterSubString); + } + } +} + +std::string LLPanelMainInventory::getLocalizedRootName() +{ + return mSingleFolderMode ? get_localized_folder_name(getCurrentSFVRoot()) : ""; +} + +LLUUID LLPanelMainInventory::getCurrentSFVRoot() +{ + if(isListViewMode()) + { + return mCombinationInventoryPanel->getSingleFolderRoot(); + } + if(isGalleryViewMode()) + { + return mCombinationGalleryPanel->getRootFolder(); + } + if(isCombinationViewMode()) + { + return mCombinationInventoryPanel->getSingleFolderRoot(); + } + return LLUUID::null; +} + +LLInventoryFilter& LLPanelMainInventory::getCurrentFilter() +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + return mCombinationGalleryPanel->getFilter(); + } + else + { + return mActivePanel->getFilter(); + } +} + +void LLPanelMainInventory::setGallerySelection(const LLUUID& item_id, bool new_window) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mCombinationGalleryPanel->changeItemSelection(item_id, true); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + if(mCombinationGalleryPanel->getFilter().checkAgainstFilterThumbnails(item_id)) + { + mCombinationGalleryPanel->changeItemSelection(item_id, false); + scrollToGallerySelection(); + } + else + { + mCombinationInventoryPanel->setSelection(item_id, true); + scrollToInvPanelSelection(); + } + } + else if (mSingleFolderMode && isListViewMode()) + { + mCombinationInventoryPanel->setSelection(item_id, true); + } +} + +void LLPanelMainInventory::scrollToGallerySelection() +{ + mCombinationGalleryPanel->scrollToShowItem(mCombinationGalleryPanel->getFirstSelectedItemID()); +} + +void LLPanelMainInventory::scrollToInvPanelSelection() +{ + mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); +} + // List Commands // //////////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h index 7aae5a0b3c..79501e63bc 100644 --- a/indra/newview/llpanelmaininventory.h +++ b/indra/newview/llpanelmaininventory.h @@ -30,6 +30,7 @@ #include "llpanel.h" #include "llinventoryobserver.h" +#include "llinventorypanel.h" #include "lldndbutton.h" #include "llfolderview.h" @@ -37,14 +38,17 @@ class LLComboBox; class LLFolderViewItem; class LLInventoryPanel; +class LLInventoryGallery; class LLSaveFolderState; class LLFilterEditor; class LLTabContainer; class LLFloaterInventoryFinder; class LLMenuButton; class LLMenuGL; +class LLSidepanelInventory; class LLToggleableMenu; class LLFloater; +class LLFloaterSidePanelContainer; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLPanelMainInventory @@ -63,6 +67,13 @@ public: BOOL postBuild(); + enum EViewModeType + { + MODE_LIST, + MODE_GALLERY, + MODE_COMBINATION + }; + virtual BOOL handleKeyHere(KEY key, MASK mask); // Inherited functionality @@ -80,6 +91,7 @@ public: LLInventoryPanel* getAllItemsPanel(); void selectAllItemsPanel(); const LLInventoryPanel* getActivePanel() const { return mActivePanel; } + void setActivePanel(); bool isRecentItemsPanelSelected(); @@ -91,13 +103,40 @@ public: void setFocusFilterEditor(); - static void newWindow(); + static LLFloaterSidePanelContainer* newWindow(); + static void newFolderWindow(LLUUID folder_id = LLUUID(), LLUUID item_to_select = LLUUID()); void toggleFindOptions(); void resetFilters(); void resetAllItemsFilters(); + void findLinks(const LLUUID& item_id, const std::string& item_name); + + void onViewModeClick(); + void toggleViewMode(); + void initSingleFolderRoot(const LLUUID& start_folder_id = LLUUID::null); + void initInventoryViews(); + void onUpFolderClicked(); + void onBackFolderClicked(); + void onForwardFolderClicked(); + void setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history = true); + void setGallerySelection(const LLUUID& item_id, bool new_window = false); + LLUUID getSingleFolderViewRoot(); + bool isSingleFolderMode() { return mSingleFolderMode; } + + void scrollToGallerySelection(); + void scrollToInvPanelSelection(); + + void setViewMode(EViewModeType mode); + bool isListViewMode() { return (mViewMode == MODE_LIST); } + bool isGalleryViewMode() { return (mViewMode == MODE_GALLERY); } + bool isCombinationViewMode() { return (mViewMode == MODE_COMBINATION); } + LLUUID getCurrentSFVRoot(); + std::string getLocalizedRootName(); + + LLInventoryFilter& getCurrentFilter(); + protected: // // Misc functions @@ -127,9 +166,15 @@ protected: bool isSaveTextureEnabled(const LLSD& userdata); void updateItemcountText(); + void updatePanelVisibility(); + void updateCombinationVisibility(); + void onFocusReceived(); void onSelectSearchType(); void updateSearchTypeCombo(); + void setSearchType(LLInventoryFilter::ESearchType type); + + LLSidepanelInventory* getParentSidepanelInventory(); private: LLFloaterInventoryFinder* getFinder(); @@ -150,7 +195,26 @@ private: std::string mCategoryCountString; LLComboBox* mSearchTypeCombo; + LLButton* mBackBtn; + LLButton* mForwardBtn; + LLButton* mUpBtn; + LLButton* mViewModeBtn; + LLLayoutPanel* mNavigationBtnsPanel; + + LLPanel* mDefaultViewPanel; + LLPanel* mCombinationViewPanel; + bool mSingleFolderMode; + EViewModeType mViewMode; + + LLInventorySingleFolderPanel* mCombinationInventoryPanel; + LLInventoryGallery* mCombinationGalleryPanel; + LLPanel* mCombinationGalleryLayoutPanel; + LLLayoutPanel* mCombinationListLayoutPanel; + LLLayoutStack* mCombinationLayoutStack; + + boost::signals2::connection mListViewRootUpdatedConnection; + boost::signals2::connection mGalleryRootUpdatedConnection; ////////////////////////////////////////////////////////////////////////////////// // List Commands // @@ -159,26 +223,37 @@ protected: void updateListCommands(); void onAddButtonClick(); void showActionMenu(LLMenuGL* menu, std::string spawning_view_name); - void onTrashButtonClick(); void onClipboardAction(const LLSD& userdata); BOOL isActionEnabled(const LLSD& command_name); BOOL isActionChecked(const LLSD& userdata); void onCustomAction(const LLSD& command_name); - bool handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept); + bool isActionVisible(const LLSD& userdata); static bool hasSettingsInventory(); + void updateTitle(); + void updateNavButtons(); + + void onCombinationRootChanged(bool gallery_clicked); + void onCombinationGallerySelectionChanged(const LLUUID& category_id); + void onCombinationInventorySelectionChanged(const std::deque<LLFolderViewItem*>& items, BOOL user_action); /** * Set upload cost in "Upload" sub menu. */ void setUploadCostIfNeeded(); + void disableAddIfNeeded(); private: - LLDragAndDropButton* mTrashButton; LLToggleableMenu* mMenuGearDefault; + LLToggleableMenu* mMenuViewDefault; LLToggleableMenu* mMenuVisibility; LLMenuButton* mGearMenuButton; + LLMenuButton* mViewMenuButton; LLMenuButton* mVisibilityMenuButton; LLHandle<LLView> mMenuAddHandle; bool mNeedUploadCost; + + bool mForceShowInvLayout; + bool mReshapeInvLayout; + LLUUID mCombInvUUIDNeedsRename; // List Commands // //////////////////////////////////////////////////////////////////////////////// }; diff --git a/indra/newview/llpanelmarketplaceinbox.cpp b/indra/newview/llpanelmarketplaceinbox.cpp index 8a86f4f63d..3638ee14fc 100644 --- a/indra/newview/llpanelmarketplaceinbox.cpp +++ b/indra/newview/llpanelmarketplaceinbox.cpp @@ -75,9 +75,6 @@ BOOL LLPanelMarketplaceInbox::postBuild() void LLPanelMarketplaceInbox::onSelectionChange() { - LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel<LLSidepanelInventory>("inventory"); - - sidepanel_inventory->updateVerbs(); } diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index 7a6631448b..e13bd0412d 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -62,10 +62,13 @@ LLInboxInventoryPanel::LLInboxInventoryPanel(const LLInboxInventoryPanel::Params : LLInventoryPanel(p) { LLInboxNewItemsStorage::getInstance()->load(); + LLInboxNewItemsStorage::getInstance()->addInboxPanel(this); } LLInboxInventoryPanel::~LLInboxInventoryPanel() -{} +{ + LLInboxNewItemsStorage::getInstance()->removeInboxPanel(this); +} void LLInboxInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) { @@ -108,6 +111,21 @@ LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * b return LLUICtrlFactory::create<LLInboxFolderViewItem>(params); } +void LLInboxInventoryPanel::onRemoveItemFreshness(const LLUUID& item_id) +{ + LLInboxFolderViewFolder* inbox_folder_view = dynamic_cast<LLInboxFolderViewFolder*>(getFolderByID(item_id)); + if(inbox_folder_view) + { + inbox_folder_view->setFresh(false); + } + + LLInboxFolderViewItem* inbox_item_view = dynamic_cast<LLInboxFolderViewItem*>(getItemByID(item_id)); + if(inbox_item_view) + { + inbox_item_view->setFresh(false); + } +} + // // LLInboxFolderViewFolder Implementation // @@ -340,4 +358,18 @@ void LLInboxNewItemsStorage::load() } } } + +void LLInboxNewItemsStorage::removeItem(const LLUUID& id) +{ + mNewItemsIDs.erase(id); + + //notify inbox panels + for (auto inbox : mInboxPanels) + { + if(inbox) + { + inbox->onRemoveItemFreshness(id); + } + } +} // eof diff --git a/indra/newview/llpanelmarketplaceinboxinventory.h b/indra/newview/llpanelmarketplaceinboxinventory.h index 3e508e801b..9eef5f209c 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.h +++ b/indra/newview/llpanelmarketplaceinboxinventory.h @@ -49,6 +49,8 @@ public: void initFromParams(const LLInventoryPanel::Params&); LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); LLFolderViewItem * createFolderViewItem(LLInvFVBridge * bridge); + + void onRemoveItemFreshness(const LLUUID& item_id); }; @@ -77,6 +79,7 @@ public: void deFreshify(); bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } protected: bool mFresh; @@ -108,6 +111,7 @@ public: void deFreshify(); bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } protected: bool mFresh; @@ -125,11 +129,16 @@ public: void load(); void addFreshItem(const LLUUID& id) { mNewItemsIDs.insert(id); } - void removeItem(const LLUUID& id) { mNewItemsIDs.erase(id); } + void removeItem(const LLUUID& id); bool isItemFresh(const LLUUID& id) { return (mNewItemsIDs.find(id) != mNewItemsIDs.end()); } + void addInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.insert(inbox); } + void removeInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.erase(inbox); } + private: std::set<LLUUID> mNewItemsIDs; + + std::set<LLInboxInventoryPanel*> mInboxPanels; }; #endif //LL_INBOXINVENTORYPANEL_H diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 6a82a3b35d..a3bbd00601 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -116,6 +116,7 @@ public: virtual PermissionMask getPermissionMask() const { return PERM_NONE; } /*virtual*/ LLFolderType::EType getPreferredType() const { return LLFolderType::FT_NONE; } virtual const LLUUID& getUUID() const { return mUUID; } + virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null;} virtual time_t getCreationDate() const; virtual void setCreationDate(time_t creation_date_utc); @@ -124,6 +125,7 @@ public: virtual BOOL canOpenItem() const { return FALSE; } virtual void closeItem() {} virtual void selectItem() {} + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} virtual BOOL isItemRenameable() const; virtual BOOL renameItem(const std::string& new_name); virtual BOOL isItemMovable() const; @@ -1398,21 +1400,6 @@ void LLPanelObjectInventory::inventoryChanged(LLViewerObject* object, { mInventoryNeedsUpdate = TRUE; } - - // refresh any properties floaters that are hanging around. - if(inventory) - { - for (LLInventoryObject::object_list_t::const_iterator iter = inventory->begin(); - iter != inventory->end(); ) - { - LLInventoryObject* item = *iter++; - LLFloaterProperties* floater = LLFloaterReg::findTypedInstance<LLFloaterProperties>("properties", item->getUUID()); - if(floater) - { - floater->refresh(); - } - } - } } void LLPanelObjectInventory::updateInventory() diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 35582d2967..4a755a6e93 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -699,8 +699,12 @@ void LLPanelOutfitEdit::onFolderViewFilterCommitted(LLUICtrl* ctrl) LLOpenFoldersWithSelection opener; mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); - - LLInventoryModelBackgroundFetch::instance().start(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } } void LLPanelOutfitEdit::onListViewFilterCommitted(LLUICtrl* ctrl) @@ -737,8 +741,12 @@ void LLPanelOutfitEdit::onSearchEdit(const std::string& string) mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); } - - LLInventoryModelBackgroundFetch::instance().start(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } if (mInventoryItemsPanel->getFilterSubString().empty() && mSearchString.empty()) { diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 531073526b..d8c34d5c40 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -87,7 +87,7 @@ BOOL LLPanelOutfitsInventory::postBuild() // ( This is only necessary if we want to show a warning if a user deletes an item that has a // a link in an outfit, see "ConfirmItemDeleteHasLinks". ) - const LLUUID &outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID &outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); if (outfits_cat.notNull()) { LLInventoryModelBackgroundFetch::instance().start(outfits_cat); @@ -166,7 +166,11 @@ void LLPanelOutfitsInventory::onSearchEdit(const std::string& string) mActivePanel->setFilterSubString(LLStringUtil::null); } - LLInventoryModelBackgroundFetch::instance().start(); + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } if (mActivePanel->getFilterSubString().empty() && string.empty()) { diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 7aabd5247a..3333c832d2 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -71,6 +71,7 @@ #include "llpanelblockedlist.h" #include "llpanelprofileclassifieds.h" #include "llpanelprofilepicks.h" +#include "llthumbnailctrl.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewermenu.h" //is_agent_mappable @@ -366,7 +367,7 @@ LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::stri httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - LL_WARNS("AvatarProperties") << result << LL_ENDL; + LL_DEBUGS("AvatarProperties") << result << LL_ENDL; if (!status) { @@ -913,7 +914,7 @@ BOOL LLPanelProfileSecondLife::postBuild() { mGroupList = getChild<LLGroupList>("group_list"); mShowInSearchCombo = getChild<LLComboBox>("show_in_search"); - mSecondLifePic = getChild<LLIconCtrl>("2nd_life_pic"); + mSecondLifePic = getChild<LLThumbnailCtrl>("2nd_life_pic"); mSecondLifePicLayout = getChild<LLPanel>("image_panel"); mDescriptionEdit = getChild<LLTextEditor>("sl_description_edit"); mAgentActionMenuButton = getChild<LLMenuButton>("agent_actions_menu"); @@ -1497,7 +1498,6 @@ void LLPanelProfileSecondLife::setLoaded() } - class LLProfileImagePicker : public LLFilePickerThread { public: @@ -1544,15 +1544,20 @@ void LLProfileImagePicker::notify(const std::vector<std::string>& filenames) const S32 MAX_DIM = 256; if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, MAX_DIM)) { - //todo: image not supported notification - LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)PROFILE_IMAGE_SL << ", failed to open image" << LL_ENDL; + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", " << notif_args["REASON"].asString() << LL_ENDL; return; } std::string cap_url = gAgent.getRegionCapability(PROFILE_IMAGE_UPLOAD_CAP); if (cap_url.empty()) { - LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)PROFILE_IMAGE_SL << ", no cap found" << LL_ENDL; + LLSD args; + args["CAPABILITY"] = PROFILE_IMAGE_UPLOAD_CAP; + LLNotificationsUtil::add("RegionCapabilityRequestError", args); + LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", no cap found" << LL_ENDL; return; } @@ -1955,30 +1960,16 @@ void LLPanelProfileSecondLife::onShowTexturePicker() mFloaterTexturePickerHandle = texture_floaterp->getHandle(); - texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLUUID id) + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&) { if (op == LLTextureCtrl::TEXTURE_SELECT) { - LLUUID image_asset_id; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterTexturePickerHandle.get(); - if (floaterp) - { - if (id.notNull()) - { - image_asset_id = id; - } - else - { - image_asset_id = floaterp->getAssetID(); - } - } - - onCommitProfileImage(image_asset_id); + onCommitProfileImage(asset_id); } }); texture_floaterp->setLocalTextureEnabled(FALSE); texture_floaterp->setBakeTextureEnabled(FALSE); - texture_floaterp->setCanApply(false, true); + texture_floaterp->setCanApply(false, true, false); parent_floater->addDependentFloater(mFloaterTexturePickerHandle); @@ -2193,7 +2184,7 @@ LLPanelProfileFirstLife::~LLPanelProfileFirstLife() BOOL LLPanelProfileFirstLife::postBuild() { mDescriptionEdit = getChild<LLTextEditor>("fl_description_edit"); - mPicture = getChild<LLIconCtrl>("real_world_pic"); + mPicture = getChild<LLThumbnailCtrl>("real_world_pic"); mUploadPhoto = getChild<LLButton>("fl_upload_image"); mChangePhoto = getChild<LLButton>("fl_change_image"); @@ -2296,29 +2287,15 @@ void LLPanelProfileFirstLife::onChangePhoto() mFloaterTexturePickerHandle = texture_floaterp->getHandle(); - texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLUUID id) + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&) { if (op == LLTextureCtrl::TEXTURE_SELECT) { - LLUUID image_asset_id; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterTexturePickerHandle.get(); - if (floaterp) - { - if (id.notNull()) - { - image_asset_id = id; - } - else - { - image_asset_id = floaterp->getAssetID(); - } - } - - onCommitPhoto(image_asset_id); + onCommitPhoto(asset_id); } }); texture_floaterp->setLocalTextureEnabled(FALSE); - texture_floaterp->setCanApply(false, true); + texture_floaterp->setCanApply(false, true, false); parent_floater->addDependentFloater(mFloaterTexturePickerHandle); diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h index d32bb943bd..11632a10ae 100644 --- a/indra/newview/llpanelprofile.h +++ b/indra/newview/llpanelprofile.h @@ -58,6 +58,7 @@ class LLTextBase; class LLMenuButton; class LLLineEditor; class LLTextEditor; +class LLThumbnailCtrl; class LLPanelProfileClassifieds; class LLPanelProfilePicks; class LLViewerFetchedTexture; @@ -192,7 +193,7 @@ private: LLGroupList* mGroupList; LLComboBox* mShowInSearchCombo; - LLIconCtrl* mSecondLifePic; + LLThumbnailCtrl* mSecondLifePic; LLPanel* mSecondLifePicLayout; LLTextEditor* mDescriptionEdit; LLMenuButton* mAgentActionMenuButton; @@ -301,7 +302,7 @@ protected: void onDiscardDescriptionChanges(); LLTextEditor* mDescriptionEdit; - LLIconCtrl* mPicture; + LLThumbnailCtrl* mPicture; LLButton* mUploadPhoto; LLButton* mChangePhoto; LLButton* mRemovePhoto; diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index bc9f0cef83..5242c4fef9 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -40,6 +40,7 @@ #include "llinventorymodel.h" #include "llinventoryobserver.h" #include "llmenubutton.h" +#include "lloutfitobserver.h" #include "llscrolllistctrl.h" #include "llviewermenu.h" #include "llviewerregion.h" @@ -218,8 +219,6 @@ LLPanelWearing::LLPanelWearing() , mIsInitialized(false) , mAttachmentsChangedConnection() { - mCategoriesObserver = new LLInventoryCategoriesObserver(); - mGearMenu = new LLWearingGearMenu(this); mContextMenu = new LLWearingContextMenu(); mAttachmentsMenu = new LLTempAttachmentsContextMenu(this); @@ -231,12 +230,6 @@ LLPanelWearing::~LLPanelWearing() delete mContextMenu; delete mAttachmentsMenu; - if (gInventory.containsObserver(mCategoriesObserver)) - { - gInventory.removeObserver(mCategoriesObserver); - } - delete mCategoriesObserver; - if (mAttachmentsChangedConnection.connected()) { mAttachmentsChangedConnection.disconnect(); @@ -281,10 +274,8 @@ void LLPanelWearing::onOpen(const LLSD& /*info*/) if (!category) return; - gInventory.addObserver(mCategoriesObserver); - // Start observing changes in Current Outfit category. - mCategoriesObserver->addCategory(cof, boost::bind(&LLWearableItemsList::updateList, mCOFItemsList, cof)); + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLWearableItemsList::updateList, mCOFItemsList, cof)); // Fetch Current Outfit contents and refresh the list to display // initially fetched items. If not all items are fetched now diff --git a/indra/newview/llpanelwearing.h b/indra/newview/llpanelwearing.h index 715404a457..18e543eec6 100644 --- a/indra/newview/llpanelwearing.h +++ b/indra/newview/llpanelwearing.h @@ -90,7 +90,6 @@ private: void getAttachmentLimitsCoro(std::string url); - LLInventoryCategoriesObserver* mCategoriesObserver; LLWearableItemsList* mCOFItemsList; LLScrollListCtrl* mTempItemsList; LLWearingGearMenu* mGearMenu; diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index cab0b74cd0..ba7af63f76 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -57,7 +57,6 @@ #include "llviewerwindow.h" #include "lldrawable.h" #include "llfloaterinspect.h" -#include "llfloaterproperties.h" #include "llfloaterreporter.h" #include "llfloaterreg.h" #include "llfloatertools.h" @@ -6858,8 +6857,6 @@ void dialog_refresh_all() gMenuAttachmentOther->arrange(); } - LLFloaterProperties::dirtyAll(); - LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance<LLFloaterInspect>("inspect"); if(inspect_instance) { diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp index f5212a3026..eaa23d6a6c 100644 --- a/indra/newview/llsettingsvo.cpp +++ b/indra/newview/llsettingsvo.cpp @@ -95,6 +95,20 @@ namespace //========================================================================= +void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID& parent_id, std::function<void(const LLUUID&)> created_cb) +{ + inventory_result_fn cb = NULL; + + if (created_cb != NULL) + { + cb = [created_cb](LLUUID asset_id, LLUUID inventory_id, LLUUID object_id, LLSD results) + { + created_cb(inventory_id); + }; + } + createNewInventoryItem(stype, parent_id, cb); +} + void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID &parent_id, inventory_result_fn callback) { LLTransactionID tid; diff --git a/indra/newview/llsettingsvo.h b/indra/newview/llsettingsvo.h index 05ec0e9275..4f410ab7d9 100644 --- a/indra/newview/llsettingsvo.h +++ b/indra/newview/llsettingsvo.h @@ -49,6 +49,7 @@ public: typedef std::function<void(LLInventoryItem *inv_item, LLSettingsBase::ptr_t settings, S32 status, LLExtStat extstat)> inventory_download_fn; typedef std::function<void(LLUUID asset_id, LLUUID inventory_id, LLUUID object_id, LLSD results)> inventory_result_fn; + static void createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID& parent_id, std::function<void(const LLUUID&)> created_cb); static void createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID &parent_id, inventory_result_fn callback = inventory_result_fn()); static void createInventoryItem(const LLSettingsBase::ptr_t &settings, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback = inventory_result_fn()); static void createInventoryItem(const LLSettingsBase::ptr_t &settings, U32 next_owner_perm, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback = inventory_result_fn()); diff --git a/indra/newview/llsidepanelinventory.cpp b/indra/newview/llsidepanelinventory.cpp index a5dcdc41ed..e970f70e92 100644 --- a/indra/newview/llsidepanelinventory.cpp +++ b/indra/newview/llsidepanelinventory.cpp @@ -73,6 +73,8 @@ static const char * const INBOX_LAYOUT_PANEL_NAME = "inbox_layout_panel"; static const char * const INVENTORY_LAYOUT_STACK_NAME = "inventory_layout_stack"; static const char * const MARKETPLACE_INBOX_PANEL = "marketplace_inbox"; +static bool sLoginCompleted = false; + // // Helpers // @@ -115,21 +117,19 @@ private: LLSidepanelInventory::LLSidepanelInventory() : LLPanel() - , mItemPanel(NULL) , mPanelMainInventory(NULL) , mInboxEnabled(false) , mCategoriesObserver(NULL) , mInboxAddedObserver(NULL) + , mInboxLayoutPanel(NULL) { //buildFromFile( "panel_inventory.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder() } LLSidepanelInventory::~LLSidepanelInventory() { - LLLayoutPanel* inbox_layout_panel = getChild<LLLayoutPanel>(INBOX_LAYOUT_PANEL_NAME); - // Save the InventoryMainPanelHeight in settings per account - gSavedPerAccountSettings.setS32("InventoryInboxHeight", inbox_layout_panel->getTargetDim()); + gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) { @@ -158,29 +158,11 @@ BOOL LLSidepanelInventory::postBuild() // UI elements from inventory panel { mInventoryPanel = getChild<LLPanel>("sidepanel_inventory_panel"); - - mInfoBtn = mInventoryPanel->getChild<LLButton>("info_btn"); - mInfoBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onInfoButtonClicked, this)); - - mShareBtn = mInventoryPanel->getChild<LLButton>("share_btn"); - mShareBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onShareButtonClicked, this)); - - mShopBtn = mInventoryPanel->getChild<LLButton>("shop_btn"); - mShopBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onShopButtonClicked, this)); - - mWearBtn = mInventoryPanel->getChild<LLButton>("wear_btn"); - mWearBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onWearButtonClicked, this)); - - mPlayBtn = mInventoryPanel->getChild<LLButton>("play_btn"); - mPlayBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onPlayButtonClicked, this)); - - mTeleportBtn = mInventoryPanel->getChild<LLButton>("teleport_btn"); - mTeleportBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onTeleportButtonClicked, this)); mPanelMainInventory = mInventoryPanel->getChild<LLPanelMainInventory>("panel_main_inventory"); mPanelMainInventory->setSelectCallback(boost::bind(&LLSidepanelInventory::onSelectionChange, this, _1, _2)); - LLTabContainer* tabs = mPanelMainInventory->getChild<LLTabContainer>("inventory filter tabs"); - tabs->setCommitCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); + //LLTabContainer* tabs = mPanelMainInventory->getChild<LLTabContainer>("inventory filter tabs"); + //tabs->setCommitCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); /* EXT-4846 : "Can we suppress the "Landmarks" and "My Favorites" folder since they have their own Task Panel?" @@ -190,25 +172,7 @@ BOOL LLSidepanelInventory::postBuild() my_inventory_panel->addHideFolderType(LLFolderType::FT_FAVORITE); */ - LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); - } - - // UI elements from item panel - { - mItemPanel = getChild<LLSidepanelItemInfo>("sidepanel__item_panel"); - - LLButton* back_btn = mItemPanel->getChild<LLButton>("back_btn"); - back_btn->setClickedCallback(boost::bind(&LLSidepanelInventory::onBackButtonClicked, this)); - } - - // UI elements from task panel - { - mTaskPanel = findChild<LLSidepanelTaskInfo>("sidepanel__task_panel"); - if (mTaskPanel) - { - LLButton* back_btn = mTaskPanel->getChild<LLButton>("back_btn"); - back_btn->setClickedCallback(boost::bind(&LLSidepanelInventory::onBackButtonClicked, this)); - } + //LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); } // Received items inbox setup @@ -220,38 +184,55 @@ BOOL LLSidepanelInventory::postBuild() inbox_button->setCommitCallback(boost::bind(&LLSidepanelInventory::onToggleInboxBtn, this)); - // Get the previous inbox state from "InventoryInboxToggleState" setting. - bool is_inbox_collapsed = !inbox_button->getToggleState(); + // For main Inventory floater: Get the previous inbox state from "InventoryInboxToggleState" setting. + // For additional Inventory floaters: Collapsed state is default. + bool is_inbox_collapsed = !inbox_button->getToggleState() || sLoginCompleted; // Restore the collapsed inbox panel state - LLLayoutPanel* inbox_panel = getChild<LLLayoutPanel>(INBOX_LAYOUT_PANEL_NAME); - inv_stack->collapsePanel(inbox_panel, is_inbox_collapsed); - if (!is_inbox_collapsed) - { - inbox_panel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); - } - - // Set the inbox visible based on debug settings (final setting comes from http request below) - enableInbox(gSavedSettings.getBOOL("InventoryDisplayInbox")); - - // Trigger callback for after login so we can setup to track inbox changes after initial inventory load - LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLSidepanelInventory::updateInbox, this)); + mInboxLayoutPanel = getChild<LLLayoutPanel>(INBOX_LAYOUT_PANEL_NAME); + inv_stack->collapsePanel(mInboxLayoutPanel, is_inbox_collapsed); + if (!is_inbox_collapsed) + { + mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); + } + + if (sLoginCompleted) + { + //save the state of Inbox panel only for main Inventory floater + inbox_button->removeControlVariable(); + inbox_button->setToggleState(false); + updateInbox(); + } + else + { + // Trigger callback for after login so we can setup to track inbox changes after initial inventory load + LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLSidepanelInventory::updateInbox, this)); + } } gSavedSettings.getControl("InventoryDisplayInbox")->getCommitSignal()->connect(boost::bind(&handleInventoryDisplayInboxChanged)); - // Update the verbs buttons state. - updateVerbs(); + LLFloater *floater = dynamic_cast<LLFloater*>(getParent()); + if (floater && floater->getKey().isUndefined() && !sLoginCompleted) + { + // Prefill inventory for primary inventory floater + // Other floaters should fill on visibility change + // + // see get_instance_num(); + // Primary inventory floater will have undefined key + initInventoryViews(); + } return TRUE; } void LLSidepanelInventory::updateInbox() { + sLoginCompleted = true; // // Track inbox folder changes // - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, true); + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); // Set up observer to listen for creation of inbox if it doesn't exist if (inbox_id.isNull()) @@ -336,8 +317,20 @@ void LLSidepanelInventory::enableInbox(bool enabled) { mInboxEnabled = enabled; - LLLayoutPanel * inbox_layout_panel = getChild<LLLayoutPanel>(INBOX_LAYOUT_PANEL_NAME); - inbox_layout_panel->setVisible(enabled); + if(!enabled || !mPanelMainInventory->isSingleFolderMode()) + { + toggleInbox(); + } +} + +void LLSidepanelInventory::hideInbox() +{ + mInboxLayoutPanel->setVisible(false); +} + +void LLSidepanelInventory::toggleInbox() +{ + mInboxLayoutPanel->setVisible(mInboxEnabled); } void LLSidepanelInventory::openInbox() @@ -367,25 +360,24 @@ void LLSidepanelInventory::onInboxChanged(const LLUUID& inbox_id) void LLSidepanelInventory::onToggleInboxBtn() { LLButton* inboxButton = getChild<LLButton>(INBOX_BUTTON_NAME); - LLLayoutPanel* inboxPanel = getChild<LLLayoutPanel>(INBOX_LAYOUT_PANEL_NAME); LLLayoutStack* inv_stack = getChild<LLLayoutStack>(INVENTORY_LAYOUT_STACK_NAME); const bool inbox_expanded = inboxButton->getToggleState(); // Expand/collapse the indicated panel - inv_stack->collapsePanel(inboxPanel, !inbox_expanded); + inv_stack->collapsePanel(mInboxLayoutPanel, !inbox_expanded); if (inbox_expanded) { - inboxPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); - if (inboxPanel->isInVisibleChain()) + mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); + if (mInboxLayoutPanel->isInVisibleChain()) { gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); } } else { - gSavedPerAccountSettings.setS32("InventoryInboxHeight", inboxPanel->getTargetDim()); + gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); } } @@ -409,47 +401,7 @@ void LLSidepanelInventory::onOpen(const LLSD& key) } #endif - if(key.size() == 0) - return; - - mItemPanel->reset(); - - if (key.has("id")) - { - mItemPanel->setItemID(key["id"].asUUID()); - if (key.has("object")) - { - mItemPanel->setObjectID(key["object"].asUUID()); - } - showItemInfoPanel(); - } - if (key.has("task")) - { - if (mTaskPanel) - mTaskPanel->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); - showTaskInfoPanel(); - } -} - -void LLSidepanelInventory::onInfoButtonClicked() -{ - LLInventoryItem *item = getSelectedItem(); - if (item) - { - mItemPanel->reset(); - mItemPanel->setItemID(item->getUUID()); - showItemInfoPanel(); - } -} - -void LLSidepanelInventory::onShareButtonClicked() -{ - LLAvatarActions::shareWithAvatars(this); -} - -void LLSidepanelInventory::onShopButtonClicked() -{ - LLWeb::loadURL(gSavedSettings.getString("MarketplaceURL")); + gAgent.showLatestFeatureNotification("inventory"); } void LLSidepanelInventory::performActionOnSelection(const std::string &action) @@ -471,47 +423,6 @@ void LLSidepanelInventory::performActionOnSelection(const std::string &action) static_cast<LLFolderViewModelItemInventory*>(current_item->getViewModelItem())->performAction(mPanelMainInventory->getActivePanel()->getModel(), action); } -void LLSidepanelInventory::onWearButtonClicked() -{ - // Get selected items set. - const std::set<LLUUID> selected_uuids_set = LLAvatarActions::getInventorySelectedUUIDs(); - if (selected_uuids_set.empty()) return; // nothing selected - - // Convert the set to a vector. - uuid_vec_t selected_uuids_vec; - for (std::set<LLUUID>::const_iterator it = selected_uuids_set.begin(); it != selected_uuids_set.end(); ++it) - { - selected_uuids_vec.push_back(*it); - } - - // Wear all selected items. - wear_multiple(selected_uuids_vec, true); -} - -void LLSidepanelInventory::onPlayButtonClicked() -{ - const LLInventoryItem *item = getSelectedItem(); - if (!item) - { - return; - } - - switch(item->getInventoryType()) - { - case LLInventoryType::IT_GESTURE: - performActionOnSelection("play"); - break; - default: - performActionOnSelection("open"); - break; - } -} - -void LLSidepanelInventory::onTeleportButtonClicked() -{ - performActionOnSelection("teleport"); -} - void LLSidepanelInventory::onBackButtonClicked() { showInventoryPanel(); @@ -519,102 +430,17 @@ void LLSidepanelInventory::onBackButtonClicked() void LLSidepanelInventory::onSelectionChange(const std::deque<LLFolderViewItem*> &items, BOOL user_action) { - updateVerbs(); -} - -void LLSidepanelInventory::showItemInfoPanel() -{ - mItemPanel->setVisible(TRUE); - if (mTaskPanel) - mTaskPanel->setVisible(FALSE); - mInventoryPanel->setVisible(FALSE); - mItemPanel->dirty(); - mItemPanel->setIsEditing(FALSE); -} - -void LLSidepanelInventory::showTaskInfoPanel() -{ - mItemPanel->setVisible(FALSE); - mInventoryPanel->setVisible(FALSE); - - if (mTaskPanel) - { - mTaskPanel->setVisible(TRUE); - mTaskPanel->dirty(); - mTaskPanel->setIsEditing(FALSE); - } } void LLSidepanelInventory::showInventoryPanel() { - mItemPanel->setVisible(FALSE); - if (mTaskPanel) - mTaskPanel->setVisible(FALSE); mInventoryPanel->setVisible(TRUE); - updateVerbs(); } -void LLSidepanelInventory::updateVerbs() +void LLSidepanelInventory::initInventoryViews() { - mInfoBtn->setEnabled(FALSE); - mShareBtn->setEnabled(FALSE); - - mWearBtn->setVisible(FALSE); - mWearBtn->setEnabled(FALSE); - mPlayBtn->setVisible(FALSE); - mPlayBtn->setEnabled(FALSE); - mPlayBtn->setToolTip(std::string("")); - mTeleportBtn->setVisible(FALSE); - mTeleportBtn->setEnabled(FALSE); - mShopBtn->setVisible(TRUE); - - mShareBtn->setEnabled(canShare()); - - const LLInventoryItem *item = getSelectedItem(); - if (!item) - return; - - bool is_single_selection = getSelectedCount() == 1; - - mInfoBtn->setEnabled(is_single_selection); - - switch(item->getInventoryType()) - { - case LLInventoryType::IT_WEARABLE: - case LLInventoryType::IT_OBJECT: - case LLInventoryType::IT_ATTACHMENT: - mWearBtn->setVisible(TRUE); - mWearBtn->setEnabled(canWearSelected()); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_SOUND: - mPlayBtn->setVisible(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setToolTip(LLTrans::getString("InventoryPlaySoundTooltip")); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_GESTURE: - mPlayBtn->setVisible(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setToolTip(LLTrans::getString("InventoryPlayGestureTooltip")); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_ANIMATION: - mPlayBtn->setVisible(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setToolTip(LLTrans::getString("InventoryPlayAnimationTooltip")); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_LANDMARK: - mTeleportBtn->setVisible(TRUE); - mTeleportBtn->setEnabled(TRUE); - mShopBtn->setVisible(FALSE); - break; - default: - break; - } + mPanelMainInventory->initInventoryViews(); } bool LLSidepanelInventory::canShare() @@ -737,12 +563,10 @@ void LLSidepanelInventory::clearSelections(bool clearMain, bool clearInbox) } } - if (clearInbox && mInboxEnabled && mInventoryPanelInbox.get()) + if (clearInbox && mInboxEnabled && !mInventoryPanelInbox.isDead()) { mInventoryPanelInbox.get()->getRootFolder()->clearSelection(); } - - updateVerbs(); } std::set<LLFolderViewItem*> LLSidepanelInventory::getInboxSelectionList() diff --git a/indra/newview/llsidepanelinventory.h b/indra/newview/llsidepanelinventory.h index a3cd20a2c6..08989bb6af 100644 --- a/indra/newview/llsidepanelinventory.h +++ b/indra/newview/llsidepanelinventory.h @@ -66,9 +66,8 @@ public: void clearSelections(bool clearMain, bool clearInbox); std::set<LLFolderViewItem*> getInboxSelectionList(); - void showItemInfoPanel(); - void showTaskInfoPanel(); void showInventoryPanel(); + void initInventoryViews(); // checks can share selected item(s) bool canShare(); @@ -76,13 +75,13 @@ public: void onToggleInboxBtn(); void enableInbox(bool enabled); + void toggleInbox(); + void hideInbox(); void openInbox(); bool isInboxEnabled() const { return mInboxEnabled; } - void updateVerbs(); - static void cleanup(); protected: @@ -103,27 +102,14 @@ protected: private: LLPanel* mInventoryPanel; // Main inventory view LLHandle<LLInventoryPanel> mInventoryPanelInbox; - LLSidepanelItemInfo* mItemPanel; // Individual item view - LLSidepanelTaskInfo* mTaskPanel; // Individual in-world object view LLPanelMainInventory* mPanelMainInventory; + LLLayoutPanel* mInboxLayoutPanel; + protected: - void onInfoButtonClicked(); - void onShareButtonClicked(); - void onShopButtonClicked(); - void onWearButtonClicked(); - void onPlayButtonClicked(); - void onTeleportButtonClicked(); void onBackButtonClicked(); private: - LLButton* mInfoBtn; - LLButton* mShareBtn; - LLButton* mWearBtn; - LLButton* mPlayBtn; - LLButton* mTeleportBtn; - LLButton* mShopBtn; - bool mInboxEnabled; LLInventoryCategoriesObserver* mCategoriesObserver; diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp index b23e24a222..d6d5a4ef2d 100644 --- a/indra/newview/llsidepaneliteminfo.cpp +++ b/indra/newview/llsidepaneliteminfo.cpp @@ -31,16 +31,23 @@ #include "llagent.h" #include "llavataractions.h" +#include "llavatarnamecache.h" #include "llbutton.h" +#include "llcallbacklist.h" #include "llcombobox.h" +#include "llfloater.h" #include "llfloaterreg.h" #include "llgroupactions.h" +#include "llgroupmgr.h" +#include "lliconctrl.h" #include "llinventorydefines.h" +#include "llinventoryicon.h" #include "llinventorymodel.h" #include "llinventoryobserver.h" #include "lllineeditor.h" #include "llradiogroup.h" #include "llslurl.h" +#include "lltexteditor.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewerobjectlist.h" @@ -73,49 +80,6 @@ private: }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLItemPropertiesObserver -// -// Helper class to watch for changes to the item. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLItemPropertiesObserver : public LLInventoryObserver -{ -public: - LLItemPropertiesObserver(LLSidepanelItemInfo* floater) - : mFloater(floater) - { - gInventory.addObserver(this); - } - virtual ~LLItemPropertiesObserver() - { - gInventory.removeObserver(this); - } - virtual void changed(U32 mask); -private: - LLSidepanelItemInfo* mFloater; // Not a handle because LLSidepanelItemInfo is managing LLItemPropertiesObserver -}; - -void LLItemPropertiesObserver::changed(U32 mask) -{ - const std::set<LLUUID>& mChangedItemIDs = gInventory.getChangedIDs(); - std::set<LLUUID>::const_iterator it; - - const LLUUID& item_id = mFloater->getItemID(); - - for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) - { - // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) - if (*it == item_id) - { - // if there's a change we're interested in. - if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) - { - mFloater->dirty(); - } - } - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLObjectInventoryObserver // // Helper class to watch for changes in an object inventory. @@ -158,36 +122,48 @@ static LLPanelInjector<LLSidepanelItemInfo> t_item_info("sidepanel_item_info"); // Default constructor LLSidepanelItemInfo::LLSidepanelItemInfo(const LLPanel::Params& p) - : LLSidepanelInventorySubpanel(p) + : LLPanel(p) , mItemID(LLUUID::null) , mObjectInventoryObserver(NULL) , mUpdatePendingId(-1) + , mIsDirty(false) /*Not ready*/ + , mParentFloater(NULL) { - mPropertiesObserver = new LLItemPropertiesObserver(this); + gInventory.addObserver(this); + gIdleCallbacks.addFunction(&LLSidepanelItemInfo::onIdle, (void*)this); } // Destroys the object LLSidepanelItemInfo::~LLSidepanelItemInfo() { - delete mPropertiesObserver; - mPropertiesObserver = NULL; + gInventory.removeObserver(this); + gIdleCallbacks.deleteFunction(&LLSidepanelItemInfo::onIdle, (void*)this); stopObjectInventoryObserver(); + + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } } // virtual BOOL LLSidepanelItemInfo::postBuild() { - LLSidepanelInventorySubpanel::postBuild(); - + mChangeThumbnailBtn = getChild<LLUICtrl>("change_thumbnail_btn"); + mItemTypeIcon = getChild<LLIconCtrl>("item_type_icon"); + mLabelOwnerName = getChild<LLTextBox>("LabelOwnerName"); + mLabelCreatorName = getChild<LLTextBox>("LabelCreatorName"); + getChild<LLLineEditor>("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); getChild<LLUICtrl>("LabelItemName")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitName,this)); - getChild<LLLineEditor>("LabelItemDesc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); getChild<LLUICtrl>("LabelItemDesc")->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this)); - // Creator information - getChild<LLUICtrl>("BtnCreator")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onClickCreator,this)); - // owner information - getChild<LLUICtrl>("BtnOwner")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onClickOwner,this)); + // Thumnail edition + mChangeThumbnailBtn->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onEditThumbnail, this)); // acquired date // owner permissions // Permissions debug text @@ -226,6 +202,12 @@ void LLSidepanelItemInfo::setItemID(const LLUUID& item_id) mItemID = item_id; mUpdatePendingId = -1; } + dirty(); +} + +void LLSidepanelItemInfo::setParentFloater(LLFloater* parent) +{ + mParentFloater = parent; } const LLUUID& LLSidepanelItemInfo::getObjectID() const @@ -249,12 +231,11 @@ void LLSidepanelItemInfo::onUpdateCallback(const LLUUID& item_id, S32 received_u void LLSidepanelItemInfo::reset() { - LLSidepanelInventorySubpanel::reset(); - mObjectID = LLUUID::null; mItemID = LLUUID::null; stopObjectInventoryObserver(); + dirty(); } void LLSidepanelItemInfo::refresh() @@ -262,60 +243,37 @@ void LLSidepanelItemInfo::refresh() LLViewerInventoryItem* item = findItem(); if(item) { - refreshFromItem(item); - updateVerbs(); + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + bool in_trash = (item->getUUID() == trash_id) || gInventory.isObjectDescendentOf(item->getUUID(), trash_id); + if (in_trash && mParentFloater) + { + // Close properties when moving to trash + // Aren't supposed to view properties from trash + mParentFloater->closeFloater(); + } + else + { + refreshFromItem(item); + } return; } - else - { - if (getIsEditing()) - { - setIsEditing(FALSE); - } - } - - if (!getIsEditing()) - { - const std::string no_item_names[]={ - "LabelItemName", - "LabelItemDesc", - "LabelCreatorName", - "LabelOwnerName" - }; - - for(size_t t=0; t<LL_ARRAY_SIZE(no_item_names); ++t) - { - getChildView(no_item_names[t])->setEnabled(false); - } - - setPropertiesFieldsEnabled(false); - - const std::string hide_names[]={ - "BaseMaskDebug", - "OwnerMaskDebug", - "GroupMaskDebug", - "EveryoneMaskDebug", - "NextMaskDebug" - }; - for(size_t t=0; t<LL_ARRAY_SIZE(hide_names); ++t) - { - getChildView(hide_names[t])->setVisible(false); - } - } - if (!item) - { - const std::string no_edit_mode_names[]={ - "BtnCreator", - "BtnOwner", - }; - for(size_t t=0; t<LL_ARRAY_SIZE(no_edit_mode_names); ++t) - { - getChildView(no_edit_mode_names[t])->setEnabled(false); - } - } - - updateVerbs(); + if (mObjectID.notNull()) + { + LLViewerObject* object = gObjectList.findObject(mObjectID); + if (object) + { + // Object exists, but object's content is not nessesary + // loaded, so assume item exists as well + return; + } + } + + if (mParentFloater) + { + // if we failed to get item, it likely no longer exists + mParentFloater->closeFloater(); + } } void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) @@ -333,7 +291,7 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) } // do not enable the UI for incomplete items. - BOOL is_complete = item->isFinished(); + bool is_complete = item->isFinished(); const BOOL cannot_restrict_permissions = LLInventoryType::cannotRestrictPermissions(item->getInventoryType()); const BOOL is_calling_card = (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD); const BOOL is_settings = (item->getInventoryType() == LLInventoryType::IT_SETTINGS); @@ -385,8 +343,22 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) getChild<LLUICtrl>("LabelItemName")->setValue(item->getName()); getChildView("LabelItemDescTitle")->setEnabled(TRUE); getChildView("LabelItemDesc")->setEnabled(is_modifiable); - getChildView("IconLocked")->setVisible(!is_modifiable); getChild<LLUICtrl>("LabelItemDesc")->setValue(item->getDescription()); + getChild<LLUICtrl>("item_thumbnail")->setValue(item->getThumbnailUUID()); + + LLUIImagePtr icon_img = LLInventoryIcon::getIcon(item->getType(), item->getInventoryType(), item->getFlags(), FALSE); + mItemTypeIcon->setImage(icon_img); + + // Style for creator and owner links + LLStyle::Params style_params; + LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.color = link_color; + style_params.readonly_color = link_color; + style_params.is_link = true; // link will be added later + const LLFontGL* fontp = mLabelCreatorName->getFont(); + style_params.font.name = LLFontGL::nameFromFont(fontp); + style_params.font.size = LLFontGL::sizeFromFont(fontp); + style_params.font.style = "UNDERLINE"; ////////////////// // CREATOR NAME // @@ -397,19 +369,34 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) if (item->getCreatorUUID().notNull()) { LLUUID creator_id = item->getCreatorUUID(); - std::string name = - LLSLURL("agent", creator_id, "completename").getSLURLString(); - getChildView("BtnCreator")->setEnabled(TRUE); + std::string slurl = + LLSLURL("agent", creator_id, "inspect").getSLURLString(); + + style_params.link_href = slurl; + + LLAvatarName av_name; + if (LLAvatarNameCache::get(creator_id, &av_name)) + { + updateCreatorName(creator_id, av_name, style_params); + } + else + { + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + mLabelCreatorName->setText(LLTrans::getString("None")); + mCreatorCacheConnection = LLAvatarNameCache::get(creator_id, boost::bind(&LLSidepanelItemInfo::updateCreatorName, this, _1, _2, style_params)); + } + getChildView("LabelCreatorTitle")->setEnabled(TRUE); - getChildView("LabelCreatorName")->setEnabled(FALSE); - getChild<LLUICtrl>("LabelCreatorName")->setValue(name); + mLabelCreatorName->setEnabled(TRUE); } else { - getChildView("BtnCreator")->setEnabled(FALSE); getChildView("LabelCreatorTitle")->setEnabled(FALSE); - getChildView("LabelCreatorName")->setEnabled(FALSE); - getChild<LLUICtrl>("LabelCreatorName")->setValue(getString("unknown_multiple")); + mLabelCreatorName->setEnabled(FALSE); + mLabelCreatorName->setValue(getString("unknown_multiple")); } //////////////// @@ -417,28 +404,60 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) //////////////// if(perm.isOwned()) { - std::string name; + std::string slurl; if (perm.isGroupOwned()) { - gCacheName->getGroupName(perm.getGroup(), name); + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(perm.getGroup()); + + slurl = LLSLURL("group", perm.getGroup(), "inspect").getSLURLString(); + style_params.link_href = slurl; + if (group_data && group_data->isGroupPropertiesDataComplete()) + { + mLabelOwnerName->setText(group_data->mName, style_params); + } + else + { + // Triggers refresh + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(perm.getGroup()); + + std::string name; + gCacheName->getGroupName(perm.getGroup(), name); + mLabelOwnerName->setText(name, style_params); + } } else { LLUUID owner_id = perm.getOwner(); - name = LLSLURL("agent", owner_id, "completename").getSLURLString(); + slurl = LLSLURL("agent", owner_id, "inspect").getSLURLString(); + + style_params.link_href = slurl; + LLAvatarName av_name; + if (LLAvatarNameCache::get(owner_id, &av_name)) + { + updateOwnerName(owner_id, av_name, style_params); + } + else + { + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + mLabelOwnerName->setText(LLTrans::getString("None")); + mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLSidepanelItemInfo::updateOwnerName, this, _1, _2, style_params)); + } } - getChildView("BtnOwner")->setEnabled(TRUE); getChildView("LabelOwnerTitle")->setEnabled(TRUE); - getChildView("LabelOwnerName")->setEnabled(FALSE); - getChild<LLUICtrl>("LabelOwnerName")->setValue(name); + mLabelOwnerName->setEnabled(TRUE); } else { - getChildView("BtnOwner")->setEnabled(FALSE); getChildView("LabelOwnerTitle")->setEnabled(FALSE); - getChildView("LabelOwnerName")->setEnabled(FALSE); - getChild<LLUICtrl>("LabelOwnerName")->setValue(getString("public")); + mLabelOwnerName->setEnabled(FALSE); + mLabelOwnerName->setValue(getString("public")); } + + // Not yet supported for task inventories + mChangeThumbnailBtn->setEnabled(mObjectID.isNull() && ALEXANDRIA_LINDEN_ID != perm.getOwner()); //////////// // ORIGIN // @@ -548,6 +567,8 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) if( gSavedSettings.getBOOL("DebugPermissions") ) { + childSetVisible("layout_debug_permissions", true); + BOOL slam_perm = FALSE; BOOL overwrite_group = FALSE; BOOL overwrite_everyone = FALSE; @@ -565,38 +586,29 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) perm_string = "B: "; perm_string += mask_to_string(base_mask); getChild<LLUICtrl>("BaseMaskDebug")->setValue(perm_string); - getChildView("BaseMaskDebug")->setVisible(TRUE); perm_string = "O: "; perm_string += mask_to_string(owner_mask); getChild<LLUICtrl>("OwnerMaskDebug")->setValue(perm_string); - getChildView("OwnerMaskDebug")->setVisible(TRUE); perm_string = "G"; perm_string += overwrite_group ? "*: " : ": "; perm_string += mask_to_string(group_mask); getChild<LLUICtrl>("GroupMaskDebug")->setValue(perm_string); - getChildView("GroupMaskDebug")->setVisible(TRUE); perm_string = "E"; perm_string += overwrite_everyone ? "*: " : ": "; perm_string += mask_to_string(everyone_mask); getChild<LLUICtrl>("EveryoneMaskDebug")->setValue(perm_string); - getChildView("EveryoneMaskDebug")->setVisible(TRUE); perm_string = "N"; perm_string += slam_perm ? "*: " : ": "; perm_string += mask_to_string(next_owner_mask); getChild<LLUICtrl>("NextMaskDebug")->setValue(perm_string); - getChildView("NextMaskDebug")->setVisible(TRUE); } else { - getChildView("BaseMaskDebug")->setVisible(FALSE); - getChildView("OwnerMaskDebug")->setVisible(FALSE); - getChildView("GroupMaskDebug")->setVisible(FALSE); - getChildView("EveryoneMaskDebug")->setVisible(FALSE); - getChildView("NextMaskDebug")->setVisible(FALSE); + childSetVisible("layout_debug_permissions", false); } ///////////// @@ -731,6 +743,68 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) } } +void LLSidepanelItemInfo::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params) +{ + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + std::string name = creator_name.getCompleteName(); + mLabelCreatorName->setText(name, style_params); +} + +void LLSidepanelItemInfo::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params) +{ + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + std::string name = owner_name.getCompleteName(); + mLabelOwnerName->setText(name, style_params); +} + +void LLSidepanelItemInfo::changed(U32 mask) +{ + const LLUUID& item_id = getItemID(); + if (getObjectID().notNull() || item_id.isNull()) + { + // Task inventory or not set up yet + return; + } + + const std::set<LLUUID>& mChangedItemIDs = gInventory.getChangedIDs(); + std::set<LLUUID>::const_iterator it; + + for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) + { + // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) + if (*it == item_id) + { + // if there's a change we're interested in. + if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) + { + dirty(); + } + } + } +} + +void LLSidepanelItemInfo::dirty() +{ + mIsDirty = true; +} + +// static +void LLSidepanelItemInfo::onIdle( void* user_data ) +{ + LLSidepanelItemInfo* self = reinterpret_cast<LLSidepanelItemInfo*>(user_data); + + if( self->mIsDirty ) + { + self->refresh(); + self->mIsDirty = false; + } +} void LLSidepanelItemInfo::setAssociatedExperience( LLHandle<LLSidepanelItemInfo> hInfo, const LLSD& experience ) { @@ -853,7 +927,7 @@ void LLSidepanelItemInfo::onCommitDescription() LLViewerInventoryItem* item = findItem(); if(!item) return; - LLLineEditor* labelItemDesc = getChild<LLLineEditor>("LabelItemDesc"); + LLTextEditor* labelItemDesc = getChild<LLTextEditor>("LabelItemDesc"); if(!labelItemDesc) { return; @@ -966,7 +1040,14 @@ void LLSidepanelItemInfo::updatePermissions() } } -// static +void LLSidepanelItemInfo::onEditThumbnail() +{ + LLSD data; + data["task_id"] = mObjectID; + data["item_id"] = mItemID; + LLFloaterReg::showInstance("change_item_thumbnail", data); +} + void LLSidepanelItemInfo::onCommitSaleInfo(LLUICtrl* ctrl) { if (ctrl) diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h index 5f29254182..b916f44520 100644 --- a/indra/newview/llsidepaneliteminfo.h +++ b/indra/newview/llsidepaneliteminfo.h @@ -27,42 +27,55 @@ #ifndef LL_LLSIDEPANELITEMINFO_H #define LL_LLSIDEPANELITEMINFO_H -#include "llsidepanelinventorysubpanel.h" +#include "llinventoryobserver.h" +#include "llpanel.h" +#include "llstyle.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLSidepanelItemInfo // Object properties for inventory side panel. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLAvatarName; class LLButton; +class LLFloater; +class LLIconCtrl; class LLViewerInventoryItem; class LLItemPropertiesObserver; class LLObjectInventoryObserver; class LLViewerObject; class LLPermissions; +class LLTextBox; -class LLSidepanelItemInfo : public LLSidepanelInventorySubpanel +class LLSidepanelItemInfo : public LLPanel, public LLInventoryObserver { public: LLSidepanelItemInfo(const LLPanel::Params& p = getDefaultParams()); virtual ~LLSidepanelItemInfo(); - /*virtual*/ BOOL postBuild(); + /*virtual*/ BOOL postBuild() override; /*virtual*/ void reset(); void setObjectID(const LLUUID& object_id); void setItemID(const LLUUID& item_id); - void setEditMode(BOOL edit); + void setParentFloater(LLFloater* parent); // For simplicity const LLUUID& getObjectID() const; const LLUUID& getItemID() const; // if received update and item id (from callback) matches internal ones, update UI void onUpdateCallback(const LLUUID& item_id, S32 received_update_id); + + void changed(U32 mask) override; + void dirty(); + + static void onIdle( void* user_data ); + void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params); + void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params); protected: - /*virtual*/ void refresh(); - /*virtual*/ void save(); + void refresh() override; + void save(); LLViewerInventoryItem* findItem() const; LLViewerObject* findObject() const; @@ -75,14 +88,23 @@ private: void startObjectInventoryObserver(); void stopObjectInventoryObserver(); void setPropertiesFieldsEnabled(bool enabled); + + boost::signals2::connection mOwnerCacheConnection; + boost::signals2::connection mCreatorCacheConnection; LLUUID mItemID; // inventory UUID for the inventory item. LLUUID mObjectID; // in-world task UUID, or null if in agent inventory. - LLItemPropertiesObserver* mPropertiesObserver; // for syncing changes to item LLObjectInventoryObserver* mObjectInventoryObserver; // for syncing changes to items inside an object // We can send multiple properties updates simultaneously, make sure only last response counts and there won't be a race condition. S32 mUpdatePendingId; + bool mIsDirty; // item properties need to be updated + LLFloater* mParentFloater; + + LLUICtrl* mChangeThumbnailBtn; + LLIconCtrl* mItemTypeIcon; + LLTextBox* mLabelOwnerName; + LLTextBox* mLabelCreatorName; // // UI Elements @@ -94,6 +116,7 @@ protected: void onCommitDescription(); void onCommitPermissions(LLUICtrl* ctrl); void updatePermissions(); + void onEditThumbnail(); void onCommitSaleInfo(LLUICtrl* ctrl); void updateSaleInfo(); void onCommitChanges(LLPointer<LLViewerInventoryItem> item); diff --git a/indra/newview/llsidepaneltaskinfo.cpp b/indra/newview/llsidepaneltaskinfo.cpp index 225751ab92..1d6b3cd80c 100644 --- a/indra/newview/llsidepaneltaskinfo.cpp +++ b/indra/newview/llsidepaneltaskinfo.cpp @@ -42,6 +42,7 @@ #include "llresmgr.h" #include "lltextbox.h" #include "llbutton.h" +#include "llcallbacklist.h" #include "llcheckboxctrl.h" #include "llviewerobject.h" #include "llselectmgr.h" @@ -75,9 +76,11 @@ static LLPanelInjector<LLSidepanelTaskInfo> t_task_info("sidepanel_task_info"); // Default constructor LLSidepanelTaskInfo::LLSidepanelTaskInfo() + : mVisibleDebugPermissions(true) // space was allocated by default { setMouseOpaque(FALSE); LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLSidepanelTaskInfo::refreshAll, this)); + gIdleCallbacks.addFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); } @@ -85,13 +88,12 @@ LLSidepanelTaskInfo::~LLSidepanelTaskInfo() { if (sActivePanel == this) sActivePanel = NULL; + gIdleCallbacks.deleteFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); } // virtual BOOL LLSidepanelTaskInfo::postBuild() { - LLSidepanelInventorySubpanel::postBuild(); - mOpenBtn = getChild<LLButton>("open_btn"); mOpenBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onOpenButtonClicked, this)); mPayBtn = getChild<LLButton>("pay_btn"); @@ -146,12 +148,12 @@ BOOL LLSidepanelTaskInfo::postBuild() mDALabelClickAction = getChildView("label click action"); mDAComboClickAction = getChild<LLComboBox>("clickaction"); mDAPathfindingAttributes = getChild<LLTextBase>("pathfinding_attributes_value"); - mDAB = getChildView("B:"); - mDAO = getChildView("O:"); - mDAG = getChildView("G:"); - mDAE = getChildView("E:"); - mDAN = getChildView("N:"); - mDAF = getChildView("F:"); + mDAB = getChild<LLUICtrl>("B:"); + mDAO = getChild<LLUICtrl>("O:"); + mDAG = getChild<LLUICtrl>("G:"); + mDAE = getChild<LLUICtrl>("E:"); + mDAN = getChild<LLUICtrl>("N:"); + mDAF = getChild<LLUICtrl>("F:"); return TRUE; } @@ -201,12 +203,22 @@ void LLSidepanelTaskInfo::disableAll() disablePermissions(); - mDAB->setVisible(FALSE); - mDAO->setVisible(FALSE); - mDAG->setVisible(FALSE); - mDAE->setVisible(FALSE); - mDAN->setVisible(FALSE); - mDAF->setVisible(FALSE); + if (mVisibleDebugPermissions) + { + mDAB->setVisible(FALSE); + mDAO->setVisible(FALSE); + mDAG->setVisible(FALSE); + mDAE->setVisible(FALSE); + mDAN->setVisible(FALSE); + mDAF->setVisible(FALSE); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = false; + } mOpenBtn->setEnabled(FALSE); mPayBtn->setEnabled(FALSE); @@ -253,6 +265,8 @@ void LLSidepanelTaskInfo::disablePermissions() void LLSidepanelTaskInfo::refresh() { + mIsDirty = false; + LLButton* btn_deed_to_group = mDeedBtn; if (btn_deed_to_group) { @@ -606,23 +620,23 @@ void LLSidepanelTaskInfo::refresh() if (gSavedSettings.getBOOL("DebugPermissions") ) { - if (valid_base_perms) - { - getChild<LLUICtrl>("B:")->setValue("B: " + mask_to_string(base_mask_on)); - getChildView("B:")->setVisible( TRUE); - - getChild<LLUICtrl>("O:")->setValue("O: " + mask_to_string(owner_mask_on)); - getChildView("O:")->setVisible( TRUE); - - getChild<LLUICtrl>("G:")->setValue("G: " + mask_to_string(group_mask_on)); - getChildView("G:")->setVisible( TRUE); - - getChild<LLUICtrl>("E:")->setValue("E: " + mask_to_string(everyone_mask_on)); - getChildView("E:")->setVisible( TRUE); - - getChild<LLUICtrl>("N:")->setValue("N: " + mask_to_string(next_owner_mask_on)); - getChildView("N:")->setVisible( TRUE); - } + if (valid_base_perms) + { + mDAB->setValue("B: " + mask_to_string(base_mask_on)); + mDAB->setVisible( TRUE); + + mDAO->setValue("O: " + mask_to_string(owner_mask_on)); + mDAO->setVisible( TRUE); + + mDAG->setValue("G: " + mask_to_string(group_mask_on)); + mDAG->setVisible( TRUE); + + mDAE->setValue("E: " + mask_to_string(everyone_mask_on)); + mDAE->setVisible( TRUE); + + mDAN->setValue("N: " + mask_to_string(next_owner_mask_on)); + mDAN->setVisible( TRUE); + } U32 flag_mask = 0x0; if (objectp->permMove()) flag_mask |= PERM_MOVE; @@ -630,18 +644,35 @@ void LLSidepanelTaskInfo::refresh() if (objectp->permCopy()) flag_mask |= PERM_COPY; if (objectp->permTransfer()) flag_mask |= PERM_TRANSFER; - getChild<LLUICtrl>("F:")->setValue("F:" + mask_to_string(flag_mask)); - getChildView("F:")->setVisible( TRUE); - } - else - { - getChildView("B:")->setVisible( FALSE); - getChildView("O:")->setVisible( FALSE); - getChildView("G:")->setVisible( FALSE); - getChildView("E:")->setVisible( FALSE); - getChildView("N:")->setVisible( FALSE); - getChildView("F:")->setVisible( FALSE); - } + mDAF->setValue("F:" + mask_to_string(flag_mask)); + mDAF->setVisible(TRUE); + + if (!mVisibleDebugPermissions) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() + (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = true; + } + } + else if (mVisibleDebugPermissions) + { + mDAB->setVisible(FALSE); + mDAO->setVisible(FALSE); + mDAG->setVisible(FALSE); + mDAE->setVisible(FALSE); + mDAN->setVisible(FALSE); + mDAF->setVisible(FALSE); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = false; + } BOOL has_change_perm_ability = FALSE; BOOL has_change_sale_ability = FALSE; @@ -864,33 +895,6 @@ void LLSidepanelTaskInfo::refresh() getChildView("label click action")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); getChildView("clickaction")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); - if (!getIsEditing()) - { - const std::string no_item_names[] = - { - "Object Name", - "Object Description", - "button set group", - "checkbox share with group", - "button deed", - "checkbox allow everyone move", - "checkbox allow everyone copy", - "checkbox for sale", - "sale type", - "Edit Cost", - "checkbox next owner can modify", - "checkbox next owner can copy", - "checkbox next owner can transfer", - "clickaction", - "search_check", - "perm_modify", - "Group Name", - }; - for (size_t t=0; t<LL_ARRAY_SIZE(no_item_names); ++t) - { - getChildView(no_item_names[t])->setEnabled( FALSE); - } - } updateVerbs(); } @@ -1203,16 +1207,6 @@ void LLSidepanelTaskInfo::onCommitIncludeInSearch(LLUICtrl* ctrl, void* data) // virtual void LLSidepanelTaskInfo::updateVerbs() { - LLSidepanelInventorySubpanel::updateVerbs(); - - /* - mOpenBtn->setVisible(!getIsEditing()); - mPayBtn->setVisible(!getIsEditing()); - mBuyBtn->setVisible(!getIsEditing()); - //const LLViewerObject *obj = getFirstSelectedObject(); - //mEditBtn->setEnabled(obj && obj->permModify()); - */ - LLSafeHandle<LLObjectSelection> object_selection = LLSelectMgr::getInstance()->getSelection(); const BOOL any_selected = (object_selection->getNumNodes() > 0); @@ -1297,6 +1291,23 @@ LLSidepanelTaskInfo* LLSidepanelTaskInfo::getActivePanel() return sActivePanel; } +void LLSidepanelTaskInfo::dirty() +{ + mIsDirty = true; +} + +// static +void LLSidepanelTaskInfo::onIdle( void* user_data ) +{ + LLSidepanelTaskInfo* self = reinterpret_cast<LLSidepanelTaskInfo*>(user_data); + + if( self->mIsDirty ) + { + self->refresh(); + self->mIsDirty = false; + } +} + LLViewerObject* LLSidepanelTaskInfo::getObject() { if (!mObject->isDead()) diff --git a/indra/newview/llsidepaneltaskinfo.h b/indra/newview/llsidepaneltaskinfo.h index dc259cb22d..852d36293b 100644 --- a/indra/newview/llsidepaneltaskinfo.h +++ b/indra/newview/llsidepaneltaskinfo.h @@ -27,8 +27,8 @@ #ifndef LL_LLSIDEPANELTASKINFO_H #define LL_LLSIDEPANELTASKINFO_H -#include "llsidepanelinventorysubpanel.h" #include "lluuid.h" +#include "llpanel.h" #include "llselectmgr.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -43,14 +43,14 @@ class LLNameBox; class LLViewerObject; class LLTextBase; -class LLSidepanelTaskInfo : public LLSidepanelInventorySubpanel +class LLSidepanelTaskInfo : public LLPanel { public: LLSidepanelTaskInfo(); virtual ~LLSidepanelTaskInfo(); - /*virtual*/ BOOL postBuild(); - /*virtual*/ void onVisibilityChange ( BOOL new_visibility ); + BOOL postBuild() override; + void onVisibilityChange ( BOOL new_visibility ) override; void setObjectSelection(LLObjectSelectionHandle selection); @@ -58,10 +58,12 @@ public: LLViewerObject* getFirstSelectedObject(); static LLSidepanelTaskInfo *getActivePanel(); + void dirty(); + static void onIdle( void* user_data ); protected: - /*virtual*/ void refresh(); // refresh all labels as needed - /*virtual*/ void save(); - /*virtual*/ void updateVerbs(); + void refresh() override; // refresh all labels as needed + void save(); + void updateVerbs(); void refreshAll(); // ignore current keyboard focus and update all fields @@ -103,6 +105,8 @@ private: LLUUID mCreatorID; LLUUID mOwnerID; LLUUID mLastOwnerID; + + bool mIsDirty; protected: void onOpenButtonClicked(); @@ -121,6 +125,10 @@ protected: private: LLPointer<LLViewerObject> mObject; LLObjectSelectionHandle mObjectSelection; + + // mVisibleDebugPermissions doesn't nessesarily matche state + // of viewes and is primarily for floater resize + bool mVisibleDebugPermissions; static LLSidepanelTaskInfo* sActivePanel; private: @@ -148,12 +156,12 @@ private: LLView* mDALabelClickAction; LLComboBox* mDAComboClickAction; LLTextBase* mDAPathfindingAttributes; - LLView* mDAB; - LLView* mDAO; - LLView* mDAG; - LLView* mDAE; - LLView* mDAN; - LLView* mDAF; + LLUICtrl* mDAB; + LLUICtrl* mDAO; + LLUICtrl* mDAG; + LLUICtrl* mDAE; + LLUICtrl* mDAN; + LLUICtrl* mDAF; }; diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index ed7e18fadc..b7a1832b17 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -51,6 +51,7 @@ #include "llviewercontrol.h" #include "llviewermenufile.h" // upload_new_resource() #include "llviewerstats.h" +#include "llviewertexturelist.h" #include "llwindow.h" #include "llworld.h" #include <boost/filesystem.hpp> @@ -873,6 +874,11 @@ LLPointer<LLImageRaw> LLSnapshotLivePreview::getEncodedImage() return mPreviewImageEncoded; } +bool LLSnapshotLivePreview::createUploadFile(const std::string &out_filename, const S32 max_image_dimentions, const S32 min_image_dimentions) +{ + return LLViewerTextureList::createUploadFile(mPreviewImage, out_filename, max_image_dimentions, min_image_dimentions); +} + // We actually estimate the data size so that we do not require actual compression when showing the preview // Note : whenever formatted image is computed, mDataSize will be updated to reflect the true size void LLSnapshotLivePreview::estimateDataSize() diff --git a/indra/newview/llsnapshotlivepreview.h b/indra/newview/llsnapshotlivepreview.h index 1f81307976..6e38a957b4 100644 --- a/indra/newview/llsnapshotlivepreview.h +++ b/indra/newview/llsnapshotlivepreview.h @@ -106,6 +106,7 @@ public: LLPointer<LLImageFormatted> getFormattedImage(); LLPointer<LLImageRaw> getEncodedImage(); + bool createUploadFile(const std::string &out_file, const S32 max_image_dimentions, const S32 min_image_dimentions); /// Sets size of preview thumbnail image and the surrounding rect. void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; } diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 1b0a814c45..ad87fca25b 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -256,7 +256,7 @@ static bool mLoginStatePastUI = false; static bool mBenefitsSuccessfullyInit = false; const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds -const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN = 3; // Give region 3 chances +const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT = 4; // Give region 4 chances std::unique_ptr<LLEventPump> LLStartUp::sStateWatcher(new LLEventStream("StartupState")); std::unique_ptr<LLStartupListener> LLStartUp::sListener(new LLStartupListener()); @@ -1412,11 +1412,18 @@ bool idle_startup() else { U32 num_retries = regionp->getNumSeedCapRetries(); - if (num_retries > MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN) + if (num_retries > MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT) { - // Region will keep trying to get capabilities, - // but for now continue as if caps were granted - LLStartUp::setStartupState(STATE_SEED_CAP_GRANTED); + LL_WARNS("AppInit") << "Failed to get capabilities. Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); } else if (num_retries > 0) { @@ -1915,6 +1922,34 @@ bool idle_startup() LLInventoryModelBackgroundFetch::instance().start(); gInventory.createCommonSystemCategories(); + LLStartUp::setStartupState(STATE_INVENTORY_CALLBACKS ); + display_startup(); + + return FALSE; + } + + //--------------------------------------------------------------------- + // STATE_INVENTORY_CALLBACKS + //--------------------------------------------------------------------- + if (STATE_INVENTORY_CALLBACKS == LLStartUp::getStartupState()) + { + if (!LLInventoryModel::isSysFoldersReady()) + { + display_startup(); + return FALSE; + } + LLInventoryModelBackgroundFetch::instance().start(); + LLUUID cof_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLViewerInventoryCategory* cof = gInventory.getCategory(cof_id); + if (cof + && cof->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // Special case, dupplicate request prevention. + // Cof folder will be requested via FetchCOF + // in appearance manager, prevent recursive fetch + cof->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + // It's debatable whether this flag is a good idea - sets all // bits, and in general it isn't true that inventory @@ -2176,7 +2211,7 @@ bool idle_startup() gAgentWearables.notifyLoadingStarted(); gAgent.setOutfitChosen(TRUE); gAgentWearables.sendDummyAgentWearablesUpdate(); - callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), set_flags_and_update_appearance); + callAfterCOFFetch(set_flags_and_update_appearance); } display_startup(); @@ -2787,7 +2822,7 @@ void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, // Not going through the processAgentInitialWearables path, so need to set this here. LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true); // Initiate creation of COF, since we're also bypassing that. - gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); ESex gender; if (gender_name == "male") @@ -2840,8 +2875,15 @@ void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, bool do_append = false; LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); // Need to fetch cof contents before we can wear. - callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), + if (do_copy) + { + callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + } + else + { + callAfterCategoryLinksFetch(cat_id, boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + } LL_DEBUGS() << "initial outfit category id: " << cat_id << LL_ENDL; } @@ -2894,6 +2936,7 @@ std::string LLStartUp::startupStateToString(EStartupState state) RTNENUM( STATE_AGENT_SEND ); RTNENUM( STATE_AGENT_WAIT ); RTNENUM( STATE_INVENTORY_SEND ); + RTNENUM(STATE_INVENTORY_CALLBACKS ); RTNENUM( STATE_MISC ); RTNENUM( STATE_PRECACHE ); RTNENUM( STATE_WEARABLES_WAIT ); @@ -2948,6 +2991,11 @@ void reset_login() LLFloaterReg::hideVisibleInstances(); LLStartUp::setStartupState( STATE_BROWSER_INIT ); + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->terminate(); + } + // Clear any verified certs and verify them again on next login // to ensure cert matches server instead of just getting reused LLPointer<LLCertificateStore> store = gSecAPIHandler->getCertificateStore(""); @@ -3621,7 +3669,7 @@ bool process_login_success_response() std::string flag = login_flags["ever_logged_in"]; if(!flag.empty()) { - gAgent.setFirstLogin((flag == "N") ? TRUE : FALSE); + gAgent.setFirstLogin(flag == "N"); } /* Flag is currently ignored by the viewer. @@ -3712,7 +3760,7 @@ bool process_login_success_response() std::string fake_initial_outfit_name = gSavedSettings.getString("FakeInitialOutfitName"); if (!fake_initial_outfit_name.empty()) { - gAgent.setFirstLogin(TRUE); + gAgent.setFirstLogin(true); sInitialOutfit = fake_initial_outfit_name; if (sInitialOutfitGender.empty()) { @@ -3747,7 +3795,9 @@ bool process_login_success_response() // Only save mfa_hash for future logins if the user wants their info remembered. - if(response.has("mfa_hash") && gSavedSettings.getBOOL("RememberUser") && gSavedSettings.getBOOL("RememberPassword")) + if(response.has("mfa_hash") + && gSavedSettings.getBOOL("RememberUser") + && LLLoginInstance::getInstance()->saveMFA()) { std::string grid(LLGridManager::getInstance()->getGridId()); std::string user_id(gUserCredential->userID()); @@ -3755,6 +3805,13 @@ bool process_login_success_response() // TODO(brad) - related to SL-17223 consider building a better interface that sync's automatically gSecAPIHandler->syncProtectedMap(); } + else if (!LLLoginInstance::getInstance()->saveMFA()) + { + std::string grid(LLGridManager::getInstance()->getGridId()); + std::string user_id(gUserCredential->userID()); + gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); + gSecAPIHandler->syncProtectedMap(); + } bool success = false; // JC: gesture loading done below, when we have an asset system diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h index fe8e215f76..921f088423 100644 --- a/indra/newview/llstartup.h +++ b/indra/newview/llstartup.h @@ -71,6 +71,7 @@ typedef enum { STATE_AGENT_SEND, // Connect to a region STATE_AGENT_WAIT, // Wait for region STATE_INVENTORY_SEND, // Do inventory transfer + STATE_INVENTORY_CALLBACKS, // Wait for missing system folders and register callbacks STATE_MISC, // Do more things (set bandwidth, start audio, save location, etc) STATE_PRECACHE, // Wait a bit for textures to download STATE_WEARABLES_WAIT, // Wait for clothing to download diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 0ce82a1297..2e137a8e12 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -176,6 +176,9 @@ LLFloaterTexturePicker::LLFloaterTexturePicker( mSelectedItemPinned( FALSE ), mCanApply(true), mCanPreview(true), + mLimitsSet(false), + mMaxDim(S32_MAX), + mMinDim(0), mPreviewSettingChanged(false), mOnFloaterCommitCallback(NULL), mOnFloaterCloseCallback(NULL), @@ -270,19 +273,37 @@ void LLFloaterTexturePicker::stopUsingPipette() } } -void LLFloaterTexturePicker::updateImageStats() +bool LLFloaterTexturePicker::updateImageStats() { + bool result = true; if (mTexturep.notNull()) { //RN: have we received header data for this image? - if (mTexturep->getFullWidth() > 0 && mTexturep->getFullHeight() > 0) + S32 width = mTexturep->getFullWidth(); + S32 height = mTexturep->getFullHeight(); + if (width > 0 && height > 0) { - std::string formatted_dims = llformat("%d x %d", mTexturep->getFullWidth(),mTexturep->getFullHeight()); - mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); - if (mOnUpdateImageStatsCallback) - { - mOnUpdateImageStatsCallback(mTexturep); - } + if ((mLimitsSet && (width != height)) + || width < mMinDim + || width > mMaxDim + || height < mMinDim + || height > mMaxDim + ) + { + std::string formatted_dims = llformat("%dx%d", width, height); + mResolutionWarning->setTextArg("[TEXDIM]", formatted_dims); + result = false; + } + else + { + std::string formatted_dims = llformat("%d x %d", width, height); + mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); + } + + if (mOnUpdateImageStatsCallback) + { + mOnUpdateImageStatsCallback(mTexturep); + } } else { @@ -293,6 +314,21 @@ void LLFloaterTexturePicker::updateImageStats() { mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("")); } + mResolutionLabel->setVisible(result); + mResolutionWarning->setVisible(!result); + + // Hide buttons and pipete to make space for mResolutionWarning + // Hiding buttons is suboptimal, but at the moment limited to inventory thumbnails + // may be this should be an info/warning icon with a tooltip? + S32 index = mModeSelector->getValue().asInteger(); + if (index == 0) + { + mDefaultBtn->setVisible(result); + mNoneBtn->setVisible(result); + mBlankBtn->setVisible(result); + mPipetteBtn->setVisible(result); + } + return result; } // virtual @@ -410,11 +446,22 @@ BOOL LLFloaterTexturePicker::postBuild() mTentativeLabel = getChild<LLTextBox>("Multiple"); mResolutionLabel = getChild<LLTextBox>("size_lbl"); + mResolutionWarning = getChild<LLTextBox>("over_limit_lbl"); - childSetAction("Default",LLFloaterTexturePicker::onBtnSetToDefault,this); - childSetAction("None", LLFloaterTexturePicker::onBtnNone,this); - childSetAction("Blank", LLFloaterTexturePicker::onBtnBlank,this); + mDefaultBtn = getChild<LLButton>("Default"); + mNoneBtn = getChild<LLButton>("None"); + mBlankBtn = getChild<LLButton>("Blank"); + mPipetteBtn = getChild<LLButton>("Pipette"); + mSelectBtn = getChild<LLButton>("Select"); + mCancelBtn = getChild<LLButton>("Cancel"); + + mDefaultBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSetToDefault,this)); + mNoneBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnNone, this)); + mBlankBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnBlank, this)); + mPipetteBtn->setCommitCallback(boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); + mSelectBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSelect, this)); + mCancelBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnCancel, this)); childSetCommitCallback("show_folders_check", onShowFolders, this); @@ -481,10 +528,6 @@ BOOL LLFloaterTexturePicker::postBuild() getChildView("show_folders_check")->setEnabled(FALSE); } - getChild<LLUICtrl>("Pipette")->setCommitCallback( boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); - childSetAction("Cancel", LLFloaterTexturePicker::onBtnCancel,this); - childSetAction("Select", LLFloaterTexturePicker::onBtnSelect,this); - // update permission filter once UI is fully initialized updateFilterPermMask(); mSavedFolderState.setApply(FALSE); @@ -504,13 +547,13 @@ void LLFloaterTexturePicker::draw() static LLCachedControl<F32> max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); - updateImageStats(); + bool valid_dims = updateImageStats(); // if we're inactive, gray out "apply immediate" checkbox getChildView("show_folders_check")->setEnabled(mActive && mCanApplyImmediately && !mNoCopyTextureSelected); - getChildView("Select")->setEnabled(mActive && mCanApply); - getChildView("Pipette")->setEnabled(mActive); - getChild<LLUICtrl>("Pipette")->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); + mSelectBtn->setEnabled(mActive && mCanApply && valid_dims); + mPipetteBtn->setEnabled(mActive); + mPipetteBtn->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); //BOOL allow_copy = FALSE; if( mOwner ) @@ -544,9 +587,9 @@ void LLFloaterTexturePicker::draw() mTentativeLabel->setVisible( FALSE ); } - getChildView("Default")->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); - getChildView("Blank")->setEnabled(mImageAssetID != mBlankImageAssetID || mTentative); - getChildView("None")->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); + mDefaultBtn->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); + mBlankBtn->setEnabled(mImageAssetID != mBlankImageAssetID || mTentative); + mNoneBtn->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); LLFloater::draw(); @@ -672,17 +715,76 @@ PermissionMask LLFloaterTexturePicker::getFilterPermMask() void LLFloaterTexturePicker::commitIfImmediateSet() { - if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply) + if (!mNoCopyTextureSelected && mCanApply) { - mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, LLUUID::null); + commitCallback(LLTextureCtrl::TEXTURE_CHANGE); } } +void LLFloaterTexturePicker::commitCallback(LLTextureCtrl::ETexturePickOp op) +{ + if (!mOnFloaterCommitCallback) + { + return; + } + LLUUID asset_id = mImageAssetID; + LLUUID inventory_id; + LLPickerSource mode = (LLPickerSource)mModeSelector->getValue().asInteger(); + + switch (mode) + { + case PICKER_INVENTORY: + { + LLFolderView* root_folder = mInventoryPanel->getRootFolder(); + if (root_folder && root_folder->getCurSelectedItem()) + { + LLFolderViewItem* last_selected = root_folder->getCurSelectedItem(); + LLFolderViewModelItemInventory* inv_view = static_cast<LLFolderViewModelItemInventory*>(last_selected->getViewModelItem()); + + LLInventoryItem* itemp = gInventory.getItem(inv_view->getUUID()); + if (itemp && itemp->getAssetUUID() == mImageAssetID) + { + inventory_id = inv_view->getUUID(); + } + else + { + mode = PICKER_UNKNOWN; // source of id unknown + } + } + else + { + mode = PICKER_UNKNOWN; // source of id unknown + } + break; + } + case PICKER_LOCAL: + { + if (!mLocalScrollCtrl->getAllSelected().empty()) + { + LLUUID temp_id = mLocalScrollCtrl->getFirstSelected()->getColumn(LOCAL_TRACKING_ID_COLUMN)->getValue().asUUID(); + asset_id = LLLocalBitmapMgr::getInstance()->getWorldID(temp_id); + } + else + { + asset_id = mImageAssetID; + mode = PICKER_UNKNOWN; // source of id unknown + } + break; + } + case PICKER_BAKE: + break; + default: + mode = PICKER_UNKNOWN; // source of id unknown + break; + } + + mOnFloaterCommitCallback(op, mode, asset_id, inventory_id); +} void LLFloaterTexturePicker::commitCancel() { if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply) { - mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, LLUUID::null); + mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, mOriginalImageAssetID, LLUUID::null); } } @@ -735,7 +837,7 @@ void LLFloaterTexturePicker::onBtnCancel(void* userdata) self->setImageID( self->mOriginalImageAssetID ); if (self->mOnFloaterCommitCallback) { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, LLUUID::null); + self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, self->mOriginalImageAssetID, LLUUID::null); } self->mViewModel->resetDirty(); self->closeFloater(); @@ -745,20 +847,7 @@ void LLFloaterTexturePicker::onBtnCancel(void* userdata) void LLFloaterTexturePicker::onBtnSelect(void* userdata) { LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - LLUUID local_id = LLUUID::null; - if (self->mOwner) - { - if (self->mLocalScrollCtrl->getVisible() && !self->mLocalScrollCtrl->getAllSelected().empty()) - { - LLUUID temp_id = self->mLocalScrollCtrl->getFirstSelected()->getColumn(LOCAL_TRACKING_ID_COLUMN)->getValue().asUUID(); - local_id = LLLocalBitmapMgr::getInstance()->getWorldID(temp_id); - } - } - - if (self->mOnFloaterCommitCallback) - { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_SELECT, local_id); - } + self->commitCallback(LLTextureCtrl::TEXTURE_SELECT); self->closeFloater(); } @@ -820,25 +909,25 @@ void LLFloaterTexturePicker::onModeSelect(LLUICtrl* ctrl, void *userdata) LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; int index = self->mModeSelector->getValue().asInteger(); - self->getChild<LLButton>("Default")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild<LLButton>("Blank")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild<LLButton>("None")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild<LLButton>("Pipette")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild<LLFilterEditor>("inventory search editor")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild<LLInventoryPanel>("inventory panel")->setVisible(index == 0 ? TRUE : FALSE); + self->mDefaultBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mBlankBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mNoneBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mPipetteBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->getChild<LLFilterEditor>("inventory search editor")->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->getChild<LLInventoryPanel>("inventory panel")->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); /*self->getChild<LLCheckBox>("show_folders_check")->setVisible(mode); no idea under which conditions the above is even shown, needs testing. */ - self->getChild<LLButton>("l_add_btn")->setVisible(index == 1 ? TRUE : FALSE); - self->getChild<LLButton>("l_rem_btn")->setVisible(index == 1 ? TRUE : FALSE); - self->getChild<LLButton>("l_upl_btn")->setVisible(index == 1 ? TRUE : FALSE); - self->getChild<LLScrollListCtrl>("l_name_list")->setVisible(index == 1 ? TRUE : FALSE); + self->getChild<LLButton>("l_add_btn")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); + self->getChild<LLButton>("l_rem_btn")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); + self->getChild<LLButton>("l_upl_btn")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); + self->getChild<LLScrollListCtrl>("l_name_list")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); - self->getChild<LLComboBox>("l_bake_use_texture_combo_box")->setVisible(index == 2 ? TRUE : FALSE); + self->getChild<LLComboBox>("l_bake_use_texture_combo_box")->setVisible(index == PICKER_BAKE ? TRUE : FALSE); self->getChild<LLCheckBoxCtrl>("hide_base_mesh_region")->setVisible(FALSE);// index == 2 ? TRUE : FALSE); - if (index == 2) + if (index == PICKER_BAKE) { self->stopUsingPipette(); @@ -979,7 +1068,7 @@ void LLFloaterTexturePicker::onLocalScrollCommit(LLUICtrl* ctrl, void* userdata) { if (self->mOnFloaterCommitCallback) { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, inworld_id); + self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, PICKER_LOCAL, inworld_id, LLUUID::null); } } } @@ -1098,10 +1187,10 @@ void LLFloaterTexturePicker::updateFilterPermMask() //mInventoryPanel->setFilterPermMask( getFilterPermMask() ); Commented out due to no-copy texture loss. } -void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply) +void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply, bool inworld_image) { - getChildRef<LLUICtrl>("Select").setEnabled(can_apply); - getChildRef<LLUICtrl>("preview_disabled").setVisible(!can_preview); + mSelectBtn->setEnabled(can_apply); + getChildRef<LLUICtrl>("preview_disabled").setVisible(!can_preview && inworld_image); getChildRef<LLUICtrl>("apply_immediate_check").setVisible(can_preview); mCanApply = can_apply; @@ -1109,6 +1198,15 @@ void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply) mPreviewSettingChanged = true; } +void LLFloaterTexturePicker::setMinDimentionsLimits(S32 min_dim) +{ + mMinDim = min_dim; + mLimitsSet = true; + + std::string formatted_dims = llformat("%dx%d", mMinDim, mMinDim); + mResolutionWarning->setTextArg("[MINTEXDIM]", formatted_dims); +} + void LLFloaterTexturePicker::onFilterEdit(const std::string& search_string ) { std::string upper_case_search_string = search_string; @@ -1405,7 +1503,7 @@ void LLTextureCtrl::showPicker(BOOL take_focus) } if (texture_floaterp) { - texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2)); + texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2, _3, _4)); } if (texture_floaterp) { @@ -1478,8 +1576,11 @@ BOOL LLTextureCtrl::handleMouseDown(S32 x, S32 y, MASK mask) showPicker(FALSE); //grab textures first... LLInventoryModelBackgroundFetch::instance().start(gInventory.findCategoryUUIDForType(LLFolderType::FT_TEXTURE)); - //...then start full inventory fetch. - LLInventoryModelBackgroundFetch::instance().start(); + //...then start full inventory fetch (should have been done on startup, but just in case. + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + LLInventoryModelBackgroundFetch::instance().start(); + } handled = TRUE; } else @@ -1519,7 +1620,7 @@ void LLTextureCtrl::onFloaterClose() mFloaterHandle.markDead(); } -void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLUUID id) +void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID& inv_id) { LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); @@ -1533,22 +1634,29 @@ void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLUUID id) else if (mCommitOnSelection || op == TEXTURE_SELECT) mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - if(floaterp->isDirty() || id.notNull()) // mModelView->setDirty does not work. + if(floaterp->isDirty() || asset_id.notNull()) // mModelView->setDirty does not work. { setTentative( FALSE ); - if (id.notNull()) - { - mImageItemID = id; - mImageAssetID = id; - } - else - { - mImageItemID = floaterp->findItemID(floaterp->getAssetID(), FALSE); - LL_DEBUGS() << "mImageItemID: " << mImageItemID << LL_ENDL; - mImageAssetID = floaterp->getAssetID(); - LL_DEBUGS() << "mImageAssetID: " << mImageAssetID << LL_ENDL; - } + switch(source) + { + case PICKER_INVENTORY: + mImageItemID = inv_id; + mImageAssetID = asset_id; + break; + case PICKER_BAKE: + case PICKER_LOCAL: + mImageItemID = LLUUID::null; + mImageAssetID = asset_id; + break; + case PICKER_UNKNOWN: + default: + mImageItemID = floaterp->findItemID(asset_id, FALSE); + mImageAssetID = asset_id; + break; + } + + LL_DEBUGS() << "mImageAssetID: " << mImageAssetID << ", mImageItemID: " << mImageItemID << LL_ENDL; if (op == TEXTURE_SELECT && mOnSelectCallback) { diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index fbb38c4464..e2bfe286d3 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -63,6 +63,14 @@ bool get_is_predefined_texture(LLUUID asset_id); LLUUID get_copy_free_item_by_asset_id(LLUUID image_id, bool no_trans_perm = false); bool get_can_copy_texture(LLUUID image_id); +enum LLPickerSource +{ + PICKER_INVENTORY, + PICKER_LOCAL, + PICKER_BAKE, + PICKER_UNKNOWN, // on cancel, default ids +}; + ////////////////////////////////////////////////////////////////////////////////////////// // LLTextureCtrl @@ -188,7 +196,7 @@ public: void closeDependentFloater(); void onFloaterClose(); - void onFloaterCommit(ETexturePickOp op, LLUUID id); + void onFloaterCommit(ETexturePickOp op, LLPickerSource source, const LLUUID& local_id, const LLUUID& inv_id); // This call is returned when a drag is detected. Your callback // should return TRUE if the drag is acceptable. @@ -256,7 +264,7 @@ private: ////////////////////////////////////////////////////////////////////////////////////////// // LLFloaterTexturePicker -typedef boost::function<void(LLTextureCtrl::ETexturePickOp op, LLUUID id)> floater_commit_callback; +typedef boost::function<void(LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID& inventory_id)> floater_commit_callback; typedef boost::function<void()> floater_close_callback; typedef boost::function<void(const LLUUID& asset_id)> set_image_asset_id_callback; typedef boost::function<void(LLPointer<LLViewerTexture> texture)> set_on_update_image_stats_callback; @@ -295,7 +303,7 @@ public: // New functions void setImageID(const LLUUID& image_asset_id, bool set_selection = true); - void updateImageStats(); + bool updateImageStats(); // true if within limits const LLUUID& getAssetID() { return mImageAssetID; } const LLUUID& findItemID(const LLUUID& asset_id, BOOL copyable_only, BOOL ignore_library = FALSE); void setCanApplyImmediately(BOOL b); @@ -309,11 +317,13 @@ public: void updateFilterPermMask(); void commitIfImmediateSet(); + void commitCallback(LLTextureCtrl::ETexturePickOp op); void commitCancel(); void onFilterEdit(const std::string& search_string); - void setCanApply(bool can_preview, bool can_apply); + void setCanApply(bool can_preview, bool can_apply, bool inworld_image = true); + void setMinDimentionsLimits(S32 min_dim); void setTextureSelectedCallback(const texture_selected_callback& cb) { mTextureSelectedCallback = cb; } void setOnFloaterCloseCallback(const floater_close_callback& cb) { mOnFloaterCloseCallback = cb; } void setOnFloaterCommitCallback(const floater_commit_callback& cb) { mOnFloaterCommitCallback = cb; } @@ -364,6 +374,7 @@ protected: LLTextBox* mTentativeLabel; LLTextBox* mResolutionLabel; + LLTextBox* mResolutionWarning; std::string mPendingName; BOOL mActive; @@ -381,11 +392,20 @@ protected: LLComboBox* mModeSelector; LLScrollListCtrl* mLocalScrollCtrl; + LLButton* mDefaultBtn; + LLButton* mNoneBtn; + LLButton* mBlankBtn; + LLButton* mPipetteBtn; + LLButton* mSelectBtn; + LLButton* mCancelBtn; private: bool mCanApply; bool mCanPreview; bool mPreviewSettingChanged; + bool mLimitsSet; + S32 mMaxDim; + S32 mMinDim; texture_selected_callback mTextureSelectedCallback; diff --git a/indra/newview/llthumbnailctrl.cpp b/indra/newview/llthumbnailctrl.cpp new file mode 100644 index 0000000000..04130fc724 --- /dev/null +++ b/indra/newview/llthumbnailctrl.cpp @@ -0,0 +1,239 @@ +/** + * @file llthumbnailctrl.cpp + * @brief LLThumbnailCtrl base class + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llthumbnailctrl.h" + +#include "linden_common.h" +#include "llagent.h" +#include "lluictrlfactory.h" +#include "lluuid.h" +#include "lltrans.h" +#include "llviewborder.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llwindow.h" + +static LLDefaultChildRegistry::Register<LLThumbnailCtrl> r("thumbnail"); + +LLThumbnailCtrl::Params::Params() +: border("border") +, border_color("border_color") +, fallback_image("fallback_image") +, image_name("image_name") +, border_visible("show_visible", false) +, interactable("interactable", false) +, show_loading("show_loading", true) +{} + +LLThumbnailCtrl::LLThumbnailCtrl(const LLThumbnailCtrl::Params& p) +: LLUICtrl(p) +, mBorderColor(p.border_color()) +, mBorderVisible(p.border_visible()) +, mFallbackImagep(p.fallback_image) +, mInteractable(p.interactable()) +, mShowLoadingPlaceholder(p.show_loading()) +, mPriority(LLGLTexture::BOOST_PREVIEW) +{ + mLoadingPlaceholderString = LLTrans::getString("texture_loading"); + + LLRect border_rect = getLocalRect(); + LLViewBorder::Params vbparams(p.border); + vbparams.name("border"); + vbparams.rect(border_rect); + mBorder = LLUICtrlFactory::create<LLViewBorder> (vbparams); + addChild(mBorder); + + if (p.image_name.isProvided()) + { + setValue(p.image_name()); + } +} + +LLThumbnailCtrl::~LLThumbnailCtrl() +{ + mTexturep = nullptr; + mImagep = nullptr; + mFallbackImagep = nullptr; +} + + +void LLThumbnailCtrl::draw() +{ + LLRect draw_rect = getLocalRect(); + + if (mBorderVisible) + { + mBorder->setKeyboardFocusHighlight(hasFocus()); + + gl_rect_2d( draw_rect, mBorderColor.get(), FALSE ); + draw_rect.stretch( -1 ); + } + + // If we're in a focused floater, don't apply the floater's alpha to the texture. + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + if( mTexturep ) + { + if( mTexturep->getComponents() == 4 ) + { + const LLColor4 color(.098f, .098f, .098f); + gl_rect_2d( draw_rect, color, TRUE); + } + + gl_draw_scaled_image( draw_rect.mLeft, draw_rect.mBottom, draw_rect.getWidth(), draw_rect.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); + + mTexturep->setKnownDrawSize(draw_rect.getWidth(), draw_rect.getHeight()); + } + else if( mImagep.notNull() ) + { + mImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha ); + } + else if (mFallbackImagep.notNull()) + { + if (draw_rect.getWidth() > mFallbackImagep->getWidth() + && draw_rect.getHeight() > mFallbackImagep->getHeight()) + { + S32 img_width = mFallbackImagep->getWidth(); + S32 img_height = mFallbackImagep->getHeight(); + S32 rect_width = draw_rect.getWidth(); + S32 rect_height = draw_rect.getHeight(); + + LLRect fallback_rect; + fallback_rect.mLeft = draw_rect.mLeft + (rect_width - img_width) / 2; + fallback_rect.mRight = fallback_rect.mLeft + img_width; + fallback_rect.mBottom = draw_rect.mBottom + (rect_height - img_height) / 2; + fallback_rect.mTop = fallback_rect.mBottom + img_height; + + mFallbackImagep->draw(fallback_rect, UI_VERTEX_COLOR % alpha); + } + else + { + mFallbackImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha); + } + } + else + { + gl_rect_2d( draw_rect, LLColor4::grey % alpha, TRUE ); + + // Draw X + gl_draw_x( draw_rect, LLColor4::black ); + } + + // Show "Loading..." string on the top left corner while this texture is loading. + // Using the discard level, do not show the string if the texture is almost but not + // fully loaded. + if (mTexturep.notNull() + && mShowLoadingPlaceholder + && !mTexturep->isFullyLoaded()) + { + U32 v_offset = 25; + LLFontGL* font = LLFontGL::getFontSansSerif(); + + // Don't show as loaded if the texture is almost fully loaded (i.e. discard1) unless god + if ((mTexturep->getDiscardLevel() > 1) || gAgent.isGodlike()) + { + font->renderUTF8( + mLoadingPlaceholderString, + 0, + llfloor(draw_rect.mLeft+3), + llfloor(draw_rect.mTop-v_offset), + LLColor4::white, + LLFontGL::LEFT, + LLFontGL::BASELINE, + LLFontGL::DROP_SHADOW); + } + } + + LLUICtrl::draw(); +} + +void LLThumbnailCtrl::clearTexture() +{ + mImageAssetID = LLUUID::null; + mTexturep = nullptr; + mImagep = nullptr; +} + +// virtual +// value might be a string or a UUID +void LLThumbnailCtrl::setValue(const LLSD& value) +{ + LLSD tvalue(value); + if (value.isString() && LLUUID::validate(value.asString())) + { + //RN: support UUIDs masquerading as strings + tvalue = LLSD(LLUUID(value.asString())); + } + + LLUICtrl::setValue(tvalue); + + mImageAssetID = LLUUID::null; + mTexturep = nullptr; + mImagep = nullptr; + + if (tvalue.isUUID()) + { + mImageAssetID = tvalue.asUUID(); + if (mImageAssetID.notNull()) + { + // Should it support baked textures? + mTexturep = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + + mTexturep->setBoostLevel(mPriority); + mTexturep->forceToSaveRawImage(0); + + S32 desired_draw_width = mTexturep->getWidth(); + S32 desired_draw_height = mTexturep->getHeight(); + + mTexturep->setKnownDrawSize(desired_draw_width, desired_draw_height); + } + } + else if (tvalue.isString()) + { + mImagep = LLUI::getUIImage(tvalue.asString(), LLGLTexture::BOOST_UI); + if (mImagep) + { + LLViewerFetchedTexture* texture = dynamic_cast<LLViewerFetchedTexture*>(mImagep->getImage().get()); + if(texture) + { + mImageAssetID = texture->getID(); + } + } + } +} + +BOOL LLThumbnailCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + if (mInteractable && getEnabled()) + { + getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; + } + return LLUICtrl::handleHover(x, y, mask); +} + + diff --git a/indra/newview/llthumbnailctrl.h b/indra/newview/llthumbnailctrl.h new file mode 100644 index 0000000000..686603b373 --- /dev/null +++ b/indra/newview/llthumbnailctrl.h @@ -0,0 +1,88 @@ +/** + * @file llthumbnailctrl.h + * @brief LLThumbnailCtrl base class + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023 Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTHUMBNAILCTRL_H +#define LL_LLTHUMBNAILCTRL_H + +#include "llui.h" +#include "lluictrl.h" +#include "llviewborder.h" // for params + +class LLUICtrlFactory; +class LLUUID; +class LLViewerFetchedTexture; + +// +// Classes +// + +// +class LLThumbnailCtrl +: public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<LLViewBorder::Params> border; + Optional<LLUIColor> border_color; + Optional<std::string> image_name; + Optional<LLUIImage*> fallback_image; + Optional<bool> border_visible; + Optional<bool> interactable; + Optional<bool> show_loading; + + Params(); + }; +protected: + LLThumbnailCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLThumbnailCtrl(); + + virtual void draw() override; + + virtual void setValue(const LLSD& value ) override; + void clearTexture(); + + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + +private: + S32 mPriority; + bool mBorderVisible; + bool mInteractable; + bool mShowLoadingPlaceholder; + std::string mLoadingPlaceholderString; + LLUUID mImageAssetID; + LLViewBorder* mBorder; + LLUIColor mBorderColor; + + LLPointer<LLViewerFetchedTexture> mTexturep; + LLPointer<LLUIImage> mImagep; + LLPointer<LLUIImage> mFallbackImagep; +}; + +#endif diff --git a/indra/newview/lltoastalertpanel.cpp b/indra/newview/lltoastalertpanel.cpp index 692e8d91a9..d35833fac9 100644 --- a/indra/newview/lltoastalertpanel.cpp +++ b/indra/newview/lltoastalertpanel.cpp @@ -279,6 +279,10 @@ LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal if (!edit_text_name.empty()) { S32 y = VPAD + BTN_HEIGHT + VPAD/2; + if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + y += EDITOR_HEIGHT; + } mLineEditor = LLUICtrlFactory::getInstance()->createFromFile<LLLineEditor>("alert_line_editor.xml", this, LLPanel::child_registry_t::instance()); if (mLineEditor) @@ -522,6 +526,10 @@ void LLToastAlertPanel::onButtonPressed( const LLSD& data, S32 button ) { response[mLineEditor->getName()] = mLineEditor->getValue(); } + if (mNotification->getForm()->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + response["ignore"] = mNotification->isIgnored(); + } response[button_data->mButton->getName()] = true; // If we declared a URL and chose the URL option, go to the url diff --git a/indra/newview/lltoolbarview.cpp b/indra/newview/lltoolbarview.cpp index 4f47c465c4..f6628293ee 100644 --- a/indra/newview/lltoolbarview.cpp +++ b/indra/newview/lltoolbarview.cpp @@ -322,9 +322,9 @@ bool LLToolBarView::loadToolbars(bool force_default) } // SL-18581: Don't show the starter avatar toolbar button for NUX users - LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); if (gAgent.isFirstLogin()) { + LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); LL_WARNS() << "First login: checking for NUX user." << LL_ENDL; if (my_outfits_cat != NULL && my_outfits_cat->getDescendentCount() > 0) { diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index b16b26b96e..78e068f808 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -764,7 +764,7 @@ void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, BOOL drop, if (!handled) { // Disallow drag and drop to 3D from the marketplace - const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplacelistings_id.notNull()) { for (S32 item_index = 0; item_index < (S32)mCargoIDs.size(); item_index++) @@ -1732,8 +1732,8 @@ EAcceptance LLToolDragAndDrop::dad3dRezAttachmentFromInv( return ACCEPT_NO; } - const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); - if(gInventory.isObjectDescendentOf(item->getUUID(), outbox_id)) + const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); + if(outbox_id.notNull() && gInventory.isObjectDescendentOf(item->getUUID(), outbox_id)) { // Legacy return ACCEPT_NO; @@ -2159,8 +2159,8 @@ EAcceptance LLToolDragAndDrop::dad3dWearCategory( return ACCEPT_NO; } - const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); - if(gInventory.isObjectDescendentOf(category->getUUID(), outbox_id)) + const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); + if(outbox_id.notNull() && gInventory.isObjectDescendentOf(category->getUUID(), outbox_id)) { // Legacy return ACCEPT_NO; diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 0f2fe1e1cd..15b95d70a9 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -56,6 +56,7 @@ #include "llfloaterbvhpreview.h" #include "llfloatercamera.h" #include "llfloatercamerapresets.h" +#include "llfloaterchangeitemthumbnail.h" #include "llfloaterchatvoicevolume.h" #include "llfloaterclassified.h" #include "llfloaterconversationlog.h" @@ -84,6 +85,7 @@ #include "llfloaterimagepreview.h" #include "llfloaterimsession.h" #include "llfloaterinspect.h" +#include "llfloaterinventorysettings.h" #include "llfloaterjoystick.h" #include "llfloaterlagmeter.h" #include "llfloaterland.h" @@ -98,12 +100,12 @@ #include "llfloatermyscripts.h" #include "llfloatermyenvironment.h" #include "llfloaternamedesc.h" +#include "llfloaternewfeaturenotification.h" #include "llfloaternotificationsconsole.h" #include "llfloaternotificationstabbed.h" #include "llfloaterobjectweights.h" #include "llfloateropenobject.h" -#include "llfloateroutfitphotopreview.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llfloaterpathfindingcharacters.h" #include "llfloaterpathfindingconsole.h" #include "llfloaterpathfindinglinksets.h" @@ -116,7 +118,6 @@ #include "llfloaterpreferenceviewadvanced.h" #include "llfloaterpreviewtrash.h" #include "llfloaterprofile.h" -#include "llfloaterproperties.h" #include "llfloaterregiondebugconsole.h" #include "llfloaterregioninfo.h" #include "llfloaterregionrestarting.h" @@ -229,6 +230,7 @@ public: "avatar_picker", "camera", "camera_presets", + "change_item_thumbnail" "classified", "add_landmark", "delete_pref_preset", @@ -247,6 +249,7 @@ public: "message_critical", // Modal!!! Login specific. If this is in use elsewhere, better to create a non modal variant "message_tos", // Modal!!! Login specific. "mute_object_by_name", + "new_feature_notification", "publish_classified", "save_pref_preset", "save_camera_preset", @@ -330,6 +333,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("camera", "floater_camera.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCamera>); LLFloaterReg::add("camera_presets", "floater_camera_presets.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCameraPresets>); LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatVoiceVolume>); + LLFloaterReg::add("change_item_thumbnail", "floater_change_item_thumbnail.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChangeItemThumbnail>); LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterClassified>); LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCompileQueue>); @@ -373,6 +377,8 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("inventory", "floater_my_inventory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>); LLFloaterReg::add("inspect", "floater_inspect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterInspect>); LLFloaterReg::add("item_properties", "floater_item_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterItemProperties>); + LLFloaterReg::add("task_properties", "floater_task_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterItemProperties>); + LLFloaterReg::add("inventory_settings", "floater_inventory_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterInventorySettings>); LLInspectAvatarUtil::registerFloater(); LLInspectGroupUtil::registerFloater(); LLInspectObjectUtil::registerFloater(); @@ -400,6 +406,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("moveview", "floater_moveview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMove>); LLFloaterReg::add("mute_object_by_name", "floater_mute_object.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGetBlockedObjectName>); LLFloaterReg::add("mini_map", "floater_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMap>); + LLFloaterReg::add("new_feature_notification", "floater_new_feature_notification.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNewFeatureNotification>); LLFloaterReg::add("notifications_console", "floater_notifications_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNotificationConsole>); @@ -408,7 +415,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("object_weights", "floater_object_weights.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterObjectWeights>); LLFloaterReg::add("openobject", "floater_openobject.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterOpenObject>); LLFloaterReg::add("outgoing_call", "floater_outgoing_call.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLOutgoingCallDialog>); - LLFloaterReg::add("outfit_photo_preview", "floater_outfit_photo_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterOutfitPhotoPreview>); LLFloaterPayUtil::registerFloater(); LLFloaterReg::add("pathfinding_characters", "floater_pathfinding_characters.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPathfindingCharacters>); @@ -436,7 +442,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("preview_sound", "floater_preview_sound.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPreviewSound>, "preview"); LLFloaterReg::add("preview_texture", "floater_preview_texture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPreviewTexture>, "preview"); LLFloaterReg::add("preview_trash", "floater_preview_trash.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPreviewTrash>); - LLFloaterReg::add("properties", "floater_inventory_item_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterProperties>); LLFloaterReg::add("publish_classified", "floater_publish_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPublishClassifiedFloater>); LLFloaterReg::add("save_pref_preset", "floater_save_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSavePrefPreset>); LLFloaterReg::add("save_camera_preset", "floater_save_camera_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSaveCameraPreset>); @@ -470,7 +475,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("scene_load_stats", "floater_scene_load_stats.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSceneLoadStats>); LLFloaterReg::add("stop_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNotRunQueue>); LLFloaterReg::add("snapshot", "floater_snapshot.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSnapshot>); - LLFloaterReg::add("simple_outfit_snapshot", "floater_simple_outfit_snapshot.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSimpleOutfitSnapshot>); + LLFloaterReg::add("simple_snapshot", "floater_simple_snapshot.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSimpleSnapshot>); LLFloaterReg::add("search", "floater_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSearch>); LLFloaterReg::add("profile", "floater_profile.xml",(LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterProfile>); LLFloaterReg::add("guidebook", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterHowTo>); diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 793eb56734..c0a74e828e 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -314,7 +314,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& uuid, time_t creation_date_utc) : LLInventoryItem(uuid, parent_uuid, perm, asset_uuid, type, inv_type, name, desc, sale_info, flags, creation_date_utc), - mIsComplete(TRUE) + mIsComplete(true) { } @@ -323,7 +323,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& item_id, const std::string& name, LLInventoryType::EType inv_type) : LLInventoryItem(), - mIsComplete(FALSE) + mIsComplete(false) { mUUID = item_id; mParentUUID = parent_id; @@ -333,7 +333,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& item_id, LLViewerInventoryItem::LLViewerInventoryItem() : LLInventoryItem(), - mIsComplete(FALSE) + mIsComplete(false) { } @@ -350,7 +350,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLViewerInventoryItem* other) LLViewerInventoryItem::LLViewerInventoryItem(const LLInventoryItem *other) : LLInventoryItem(other), - mIsComplete(TRUE) + mIsComplete(true) { } @@ -432,48 +432,43 @@ void LLViewerInventoryItem::fetchFromServer(void) const { if(!mIsComplete) { - std::string url; - - LLViewerRegion* region = gAgent.getRegion(); - // we have to check region. It can be null after region was destroyed. See EXT-245 - if (region) - { - if (gAgent.getID() != mPermissions.getOwner()) - { - url = region->getCapability("FetchLib2"); - } - else - { - url = region->getCapability("FetchInventory2"); - } - } - else - { - LL_WARNS(LOG_INV) << "Agent Region is absent" << LL_ENDL; - } - - if (!url.empty()) - { - LLSD body; - body["agent_id"] = gAgent.getID(); - body["items"][0]["owner_id"] = mPermissions.getOwner(); - body["items"][0]["item_id"] = mUUID; - - LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body)); - gInventory.requestPost(true, url, body, handler, "Inventory Item"); - } - else - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("FetchInventory"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("InventoryData"); - msg->addUUID("OwnerID", mPermissions.getOwner()); - msg->addUUID("ItemID", mUUID); - gAgent.sendReliableMessage(); - } + if (AISAPI::isAvailable()) // AIS v 3 + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(mUUID); + } + else + { + std::string url; + + LLViewerRegion* region = gAgent.getRegion(); + // we have to check region. It can be null after region was destroyed. See EXT-245 + if (region) + { + if (gAgent.getID() != mPermissions.getOwner()) + { + url = region->getCapability("FetchLib2"); + } + else + { + url = region->getCapability("FetchInventory2"); + } + } + else + { + LL_WARNS(LOG_INV) << "Agent Region is absent" << LL_ENDL; + } + + if (!url.empty()) + { + LLSD body; + body["agent_id"] = gAgent.getID(); + body["items"][0]["owner_id"] = mPermissions.getOwner(); + body["items"][0]["item_id"] = mUUID; + + LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body)); + gInventory.requestPost(true, url, body, handler, "Inventory Item"); + } + } } } @@ -568,7 +563,8 @@ LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& uuid, LLInventoryCategory(uuid, parent_uuid, pref, name), mOwnerID(owner_id), mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), - mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), + mFetching(FETCH_NONE) { mDescendentsRequested.reset(); } @@ -576,7 +572,8 @@ LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& uuid, LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& owner_id) : mOwnerID(owner_id), mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), - mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), + mFetching(FETCH_NONE) { mDescendentsRequested.reset(); } @@ -584,6 +581,7 @@ LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& owner_id) : LLViewerInventoryCategory::LLViewerInventoryCategory(const LLViewerInventoryCategory* other) { copyViewerCategory(other); + mFetching = FETCH_NONE; } LLViewerInventoryCategory::~LLViewerInventoryCategory() @@ -659,7 +657,6 @@ bool LLViewerInventoryCategory::fetch() mDescendentsRequested.reset(); mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); - std::string url; if (gAgent.getRegion()) { @@ -669,15 +666,52 @@ bool LLViewerInventoryCategory::fetch() { LL_WARNS(LOG_INV) << "agent region is null" << LL_ENDL; } - if (!url.empty()) //Capability found. Build up LLSD and use it. + if (!url.empty() || AISAPI::isAvailable()) { - LLInventoryModelBackgroundFetch::instance().start(mUUID, false); + LLInventoryModelBackgroundFetch::instance().start(mUUID, false); } return true; } return false; } +LLViewerInventoryCategory::EFetchType LLViewerInventoryCategory::getFetching() +{ + // if timer hasn't expired, request was scheduled, but not in progress + // if mFetching request was actually started + if (mDescendentsRequested.hasExpired()) + { + mFetching = FETCH_NONE; + } + return mFetching; +} + +void LLViewerInventoryCategory::setFetching(LLViewerInventoryCategory::EFetchType fetching) +{ + if (fetching > mFetching) // allow a switch from normal to recursive + { + if (mDescendentsRequested.hasExpired() || (mFetching == FETCH_NONE)) + { + mDescendentsRequested.reset(); + if (AISAPI::isAvailable()) + { + mDescendentsRequested.setTimerExpirySec(AISAPI::HTTP_TIMEOUT); + } + else + { + const F32 FETCH_TIMER_EXPIRY = 30.0f; + mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } + } + mFetching = fetching; + } + else if (fetching == FETCH_NONE) + { + mDescendentsRequested.stop(); + mFetching = fetching; + } +} + S32 LLViewerInventoryCategory::getViewerDescendentCount() const { LLInventoryModel::cat_array_t* cats; @@ -1002,13 +1036,18 @@ void create_notecard_cb(const LLUUID& inv_item) LLInventoryCallbackManager gInventoryCallbacks; -void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, U8 subtype, - U32 next_owner_perm, - LLPointer<LLInventoryCallback> cb) +void create_inventory_item( + const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& parent_id, + const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + U8 subtype, + U32 next_owner_perm, + LLPointer<LLInventoryCallback> cb) { //check if name is equal to one of special inventory items names //EXT-5839 @@ -1029,6 +1068,54 @@ void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, } } +#ifdef USE_AIS_FOR_NC + // D567 18.03.2023 not yet implemented within AIS3 + if (AISAPI::isAvailable()) + { + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["items"] = LLSD::emptyArray(); + + LLPermissions perms; + perms.init( + gAgentID, + gAgentID, + LLUUID::null, + LLUUID::null); + perms.initMasks( + PERM_ALL, + PERM_ALL, + PERM_NONE, + PERM_NONE, + next_owner_perm); + + LLUUID null_id; + LLPointer<LLViewerInventoryItem> item = new LLViewerInventoryItem( + null_id, /*don't know yet*/ + parent_id, + perms, + null_id, /*don't know yet*/ + asset_type, + inv_type, + server_name, + desc, + LLSaleInfo(), + 0, + 0 /*don't know yet, whenever server creates it*/); + LLSD item_sd = item->asLLSD(); + new_inventory["items"].append(item_sd); + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::CreateInventory( + parent_id, + new_inventory, + cr); + return; + } + else + { + LL_WARNS() << "AIS v3 not available" << LL_ENDL; + } +#endif + LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_CreateInventoryItem); msg->nextBlock(_PREHASH_AgentData); @@ -1036,7 +1123,7 @@ void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, msg->addUUIDFast(_PREHASH_SessionID, session_id); msg->nextBlock(_PREHASH_InventoryBlock); msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb)); - msg->addUUIDFast(_PREHASH_FolderID, parent); + msg->addUUIDFast(_PREHASH_FolderID, parent_id); msg->addUUIDFast(_PREHASH_TransactionID, transaction_id); msg->addU32Fast(_PREHASH_NextOwnerMask, next_owner_perm); msg->addS8Fast(_PREHASH_Type, (S8)asset_type); @@ -1292,17 +1379,15 @@ void update_inventory_category( LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << LL_ENDL; if(obj) { - if (LLFolderType::lookupIsProtectedType(obj->getPreferredType())) + if (LLFolderType::lookupIsProtectedType(obj->getPreferredType()) + && (updates.size() != 1 || !updates.has("thumbnail"))) { LLNotificationsUtil::add("CannotModifyProtectedCategories"); return; } - LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(obj); - new_cat->fromLLSD(updates); - LLSD new_llsd = new_cat->asLLSD(); AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::UpdateCategory(cat_id, new_llsd, cr); + AISAPI::UpdateCategory(cat_id, updates, cr); } } @@ -1354,25 +1439,10 @@ void remove_inventory_item( gInventory.onObjectDeletedFromServer(item_id); } } - else // no cap - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RemoveInventoryItem); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_ItemID, item_id); - gAgent.sendReliableMessage(); - - // Update inventory and call callback immediately since - // message-based system has no callback mechanism (!) - gInventory.onObjectDeletedFromServer(item_id); - if (cb) - { - cb->fire(item_id); - } - } + else + { + LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; + } } else { @@ -1501,28 +1571,10 @@ void purge_descendents_of(const LLUUID& id, LLPointer<LLInventoryCallback> cb) AISAPI::completion_t cr = (cb) ? boost::bind(&doInventoryCb, cb, _1) : AISAPI::completion_t(); AISAPI::PurgeDescendents(id, cr); } - else // no cap - { - // Fast purge - LL_DEBUGS(LOG_INV) << "purge_descendents_of fast case " << cat->getName() << LL_ENDL; - - // send it upstream - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("PurgeInventoryDescendents"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("InventoryData"); - msg->addUUID("FolderID", id); - gAgent.sendReliableMessage(); - - // Update model immediately because there is no callback mechanism. - gInventory.onDescendentsPurgedFromServer(id); - if (cb) - { - cb->fire(id); - } - } + else + { + LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; + } } } @@ -1594,13 +1646,14 @@ void create_new_item(const std::string& name, const LLUUID& parent_id, LLAssetType::EType asset_type, LLInventoryType::EType inv_type, - U32 next_owner_perm) + U32 next_owner_perm, + std::function<void(const LLUUID&)> created_cb = NULL) { std::string desc; LLViewerAssetType::generateDescriptionFor(asset_type, desc); next_owner_perm = (next_owner_perm) ? next_owner_perm : PERM_MOVE | PERM_TRANSFER; - LLPointer<LLInventoryCallback> cb = NULL; + LLPointer<LLBoostFuncInventoryCallback> cb = NULL; switch (inv_type) { @@ -1624,9 +1677,17 @@ void create_new_item(const std::string& name, next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Notecards"); break; } - default: - break; - } + + default: + { + cb = new LLBoostFuncInventoryCallback(); + break; + } + } + if (created_cb != NULL) + { + cb->addOnFireFunc(created_cb); + } create_inventory_item(gAgent.getID(), gAgent.getSessionID(), @@ -1678,57 +1739,86 @@ const std::string NEW_GESTURE_NAME = "New Gesture"; // *TODO:Translate? (probabl // ! REFACTOR ! Really need to refactor this so that it's not a bunch of if-then statements... void menu_create_inventory_item(LLInventoryPanel* panel, LLFolderBridge *bridge, const LLSD& userdata, const LLUUID& default_parent_uuid) { - std::string type_name = userdata.asString(); - - if (("inbox" == type_name) || ("category" == type_name) || ("current" == type_name) || ("outfit" == type_name) || ("my_otfts" == type_name)) - { - LLFolderType::EType preferred_type = LLFolderType::lookup(type_name); + menu_create_inventory_item(panel, bridge ? bridge->getUUID() : LLUUID::null, userdata, default_parent_uuid); +} - LLUUID parent_id; - if (bridge) - { - parent_id = bridge->getUUID(); - } - else if (default_parent_uuid.notNull()) - { - parent_id = default_parent_uuid; - } - else - { - parent_id = gInventory.getRootFolderID(); - } +void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid, std::function<void(const LLUUID&)> created_cb) +{ + std::string type_name = userdata.asString(); + + if (("inbox" == type_name) || ("category" == type_name) || ("current" == type_name) || ("outfit" == type_name) || ("my_otfts" == type_name)) + { + LLFolderType::EType preferred_type = LLFolderType::lookup(type_name); - LLUUID category = gInventory.createNewCategory(parent_id, preferred_type, LLStringUtil::null); - gInventory.notifyObservers(); - panel->setSelectionByID(category, TRUE); - } - else if ("lsl" == type_name) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); - create_new_item(NEW_LSL_NAME, - parent_id, - LLAssetType::AT_LSL_TEXT, - LLInventoryType::IT_LSL, - PERM_MOVE | PERM_TRANSFER); // overridden in create_new_item - } - else if ("notecard" == type_name) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_NOTECARD); - create_new_item(NEW_NOTECARD_NAME, - parent_id, - LLAssetType::AT_NOTECARD, - LLInventoryType::IT_NOTECARD, - PERM_ALL); // overridden in create_new_item - } - else if ("gesture" == type_name) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - create_new_item(NEW_GESTURE_NAME, - parent_id, - LLAssetType::AT_GESTURE, - LLInventoryType::IT_GESTURE, - PERM_ALL); // overridden in create_new_item - } + LLUUID parent_id; + if (dest_id.notNull()) + { + parent_id = dest_id; + } + else if (default_parent_uuid.notNull()) + { + parent_id = default_parent_uuid; + } + else + { + parent_id = gInventory.getRootFolderID(); + } + + std::function<void(const LLUUID&)> callback_cat_created = NULL; + if (panel) + { + LLHandle<LLPanel> handle = panel->getHandle(); + callback_cat_created = [handle](const LLUUID& new_category_id) + { + gInventory.notifyObservers(); + LLInventoryPanel* panel = static_cast<LLInventoryPanel*>(handle.get()); + if (panel) + { + panel->setSelectionByID(new_category_id, TRUE); + } + LL_DEBUGS(LOG_INV) << "Done creating inventory: " << new_category_id << LL_ENDL; + }; + } + else if (created_cb != NULL) + { + callback_cat_created = created_cb; + } + gInventory.createNewCategory( + parent_id, + preferred_type, + LLStringUtil::null, + callback_cat_created); + } + else if ("lsl" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); + create_new_item(NEW_LSL_NAME, + parent_id, + LLAssetType::AT_LSL_TEXT, + LLInventoryType::IT_LSL, + PERM_MOVE | PERM_TRANSFER, + created_cb); // overridden in create_new_item + } + else if ("notecard" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_NOTECARD); + create_new_item(NEW_NOTECARD_NAME, + parent_id, + LLAssetType::AT_NOTECARD, + LLInventoryType::IT_NOTECARD, + PERM_ALL, + created_cb); // overridden in create_new_item + } + else if ("gesture" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + create_new_item(NEW_GESTURE_NAME, + parent_id, + LLAssetType::AT_GESTURE, + LLInventoryType::IT_GESTURE, + PERM_ALL, + created_cb); // overridden in create_new_item + } else if (("sky" == type_name) || ("water" == type_name) || ("daycycle" == type_name)) { LLSettingsType::type_e stype(LLSettingsType::ST_NONE); @@ -1751,25 +1841,28 @@ void menu_create_inventory_item(LLInventoryPanel* panel, LLFolderBridge *bridge, return; } - LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS); + LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS); - LLSettingsVOBase::createNewInventoryItem(stype, parent_id); + LLSettingsVOBase::createNewInventoryItem(stype, parent_id, created_cb); + } + else + { + // Use for all clothing and body parts. Adding new wearable types requires updating LLWearableDictionary. + LLWearableType::EType wearable_type = LLWearableType::getInstance()->typeNameToType(type_name); + if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT) + { + const LLUUID parent_id = dest_id; + LLAgentWearables::createWearable(wearable_type, false, parent_id, created_cb); + } + else + { + LL_WARNS(LOG_INV) << "Can't create unrecognized type " << type_name << LL_ENDL; + } + } + if(panel) + { + panel->getRootFolder()->setNeedsAutoRename(TRUE); } - else - { - // Use for all clothing and body parts. Adding new wearable types requires updating LLWearableDictionary. - LLWearableType::EType wearable_type = LLWearableType::getInstance()->typeNameToType(type_name); - if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : LLUUID::null; - LLAgentWearables::createWearable(wearable_type, false, parent_id); - } - else - { - LL_WARNS(LOG_INV) << "Can't create unrecognized type " << type_name << LL_ENDL; - } - } - panel->getRootFolder()->setNeedsAutoRename(TRUE); } LLAssetType::EType LLViewerInventoryItem::getType() const @@ -1895,6 +1988,25 @@ const LLSaleInfo& LLViewerInventoryItem::getSaleInfo() const return LLInventoryItem::getSaleInfo(); } +const LLUUID& LLViewerInventoryItem::getThumbnailUUID() const +{ + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_TEXTURE) + { + return mAssetUUID; + } + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK) + { + LLViewerInventoryItem *linked_item = gInventory.getItem(mAssetUUID); + return linked_item ? linked_item->getThumbnailUUID() : LLUUID::null; + } + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK_FOLDER) + { + LLViewerInventoryCategory *linked_cat = gInventory.getCategory(mAssetUUID); + return linked_cat ? linked_cat->getThumbnailUUID() : LLUUID::null; + } + return mThumbnailUUID; +} + LLInventoryType::EType LLViewerInventoryItem::getInventoryType() const { if (const LLViewerInventoryItem *linked_item = getLinkedItem()) diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index 24b632632b..6d3676ba2e 100644 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -72,6 +72,7 @@ public: virtual const LLUUID& getCreatorUUID() const; virtual const std::string& getDescription() const; virtual const LLSaleInfo& getSaleInfo() const; + virtual const LLUUID& getThumbnailUUID() const; virtual LLInventoryType::EType getInventoryType() const; virtual bool isWearableType() const; virtual LLWearableType::EType getWearableType() const; @@ -134,8 +135,8 @@ public: virtual BOOL importLegacyStream(std::istream& input_stream); // new methods - BOOL isFinished() const { return mIsComplete; } - void setComplete(BOOL complete) { mIsComplete = complete; } + bool isFinished() const { return mIsComplete; } + void setComplete(bool complete) { mIsComplete = complete; } //void updateAssetOnServer() const; virtual void setTransactionID(const LLTransactionID& transaction_id); @@ -163,7 +164,7 @@ public: BOOL regenerateLink(); public: - BOOL mIsComplete; + bool mIsComplete; LLTransactionID mTransactionID; }; @@ -208,9 +209,18 @@ public: S32 getVersion() const; void setVersion(S32 version); - // Returns true if a fetch was issued. + // Returns true if a fetch was issued (not nessesary in progress). bool fetch(); + typedef enum { + FETCH_NONE = 0, + FETCH_NORMAL, + FETCH_RECURSIVE, + } EFetchType; + EFetchType getFetching(); + // marks as fetch being in progress or as done + void setFetching(EFetchType); + // used to help make caching more robust - for example, if // someone is getting 4 packets but logs out after 3. the viewer // may never know the cache is wrong. @@ -239,12 +249,14 @@ protected: LLUUID mOwnerID; S32 mVersion; S32 mDescendentCount; + EFetchType mFetching; LLFrameTimer mDescendentsRequested; }; class LLInventoryCallback : public LLRefCount { public: + virtual ~LLInventoryCallback() {} virtual void fire(const LLUUID& inv_item) = 0; }; @@ -283,17 +295,29 @@ class LLBoostFuncInventoryCallback: public LLInventoryCallback { public: - LLBoostFuncInventoryCallback(inventory_func_type fire_func = no_op_inventory_func, + LLBoostFuncInventoryCallback(inventory_func_type fire_func, nullary_func_type destroy_func = no_op): - mFireFunc(fire_func), mDestroyFunc(destroy_func) { + mFireFuncs.push_back(fire_func); } + LLBoostFuncInventoryCallback() + { + } + + void addOnFireFunc(inventory_func_type fire_func) + { + mFireFuncs.push_back(fire_func); + } + // virtual void fire(const LLUUID& item_id) { - mFireFunc(item_id); + for (inventory_func_type &func: mFireFuncs) + { + func(item_id); + } } // virtual @@ -304,7 +328,7 @@ public: private: - inventory_func_type mFireFunc; + std::list<inventory_func_type> mFireFuncs; nullary_func_type mDestroyFunc; }; @@ -445,6 +469,8 @@ void menu_create_inventory_item(LLInventoryPanel* root, const LLSD& userdata, const LLUUID& default_parent_uuid = LLUUID::null); +void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid = LLUUID::null, std::function<void(const LLUUID&)> folder_created_cb = NULL); + void slam_inventory_folder(const LLUUID& folder_id, const LLSD& contents, LLPointer<LLInventoryCallback> cb); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index cc59e559e9..8686fad3e6 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -402,14 +402,14 @@ void set_merchant_SLM_menu() LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); gToolBarView->enableCommand(command->id(), true); - const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplacelistings_id.isNull()) { U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); bool is_merchant = (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) || (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT); if (is_merchant) { - gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, true); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MARKETPLACE_LISTINGS); LL_WARNS("SLM") << "Creating the marketplace listings folder for a merchant" << LL_ENDL; } } @@ -3065,9 +3065,7 @@ void handle_object_inspect() LLViewerObject* selected_objectp = selection->getFirstRootObject(); if (selected_objectp) { - LLSD key; - key["task"] = "task"; - LLFloaterSidePanelContainer::showPanel("inventory", key); + LLFloaterReg::showInstance("task_properties"); } /* diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 2634c8d908..17e89e1850 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -38,7 +38,7 @@ #include "llfloatermap.h" #include "llfloatermodelpreview.h" #include "llfloatersnapshot.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llimage.h" #include "llimagebmp.h" #include "llimagepng.h" @@ -735,9 +735,7 @@ class LLFileEnableCloseAllWindows : public view_listener_t bool handleEvent(const LLSD& userdata) { LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); - LLFloaterSimpleOutfitSnapshot* floater_outfit_snapshot = LLFloaterSimpleOutfitSnapshot::findInstance(); - bool is_floaters_snapshot_opened = (floater_snapshot && floater_snapshot->isInVisibleChain()) - || (floater_outfit_snapshot && floater_outfit_snapshot->isInVisibleChain()); + bool is_floaters_snapshot_opened = (floater_snapshot && floater_snapshot->isInVisibleChain()); bool open_children = gFloaterView->allChildrenClosed() && !is_floaters_snapshot_opened; return !open_children && !LLNotificationsUI::LLToast::isAlertToastShown(); } @@ -752,9 +750,6 @@ class LLFileCloseAllWindows : public view_listener_t LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); if (floater_snapshot) floater_snapshot->closeFloater(app_quitting); - LLFloaterSimpleOutfitSnapshot* floater_outfit_snapshot = LLFloaterSimpleOutfitSnapshot::findInstance(); - if (floater_outfit_snapshot) - floater_outfit_snapshot->closeFloater(app_quitting); if (gMenuHolder) gMenuHolder->hideMenus(); return true; } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 7421bba733..f3288a5300 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -76,6 +76,7 @@ #include "llnotifications.h" #include "llnotificationsutil.h" #include "llpanelgrouplandmoney.h" +#include "llpanelmaininventory.h" #include "llrecentpeople.h" #include "llscriptfloater.h" #include "llscriptruntimeperms.h" @@ -1537,11 +1538,35 @@ void open_inventory_offer(const uuid_vec_t& objects, const std::string& from_nam } //////////////////////////////////////////////////////////////////////////////// + static LLUICachedControl<bool> find_original_new_floater("FindOriginalOpenWindow", false); + //show in a new single-folder window + if(find_original_new_floater && !from_name.empty()) + { + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj && obj->getParentUUID().notNull()) + { + if (obj->getActualType() == LLAssetType::AT_CATEGORY) + { + LLPanelMainInventory::newFolderWindow(obj_id); + } + else + { + LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), obj_id); + } + } + } + else + { // Highlight item const BOOL auto_open = gSavedSettings.getBOOL("ShowInInventory") && // don't open if showininventory is false !from_name.empty(); // don't open if it's not from anyone. - LLInventoryPanel::openInventoryPanelAndSetSelection(auto_open, obj_id); + if(auto_open) + { + LLFloaterReg::showInstance("inventory"); + } + LLInventoryPanel::openInventoryPanelAndSetSelection(auto_open, obj_id, true); + } } } @@ -5918,42 +5943,47 @@ void container_inventory_arrived(LLViewerObject* object, { // create a new inventory category to put this in LLUUID cat_id; - cat_id = gInventory.createNewCategory(gInventory.getRootFolderID(), - LLFolderType::FT_NONE, - LLTrans::getString("AcquiredItems")); + gInventory.createNewCategory( + gInventory.getRootFolderID(), + LLFolderType::FT_NONE, + LLTrans::getString("AcquiredItems"), + [inventory](const LLUUID &new_cat_id) + { + LLInventoryObject::object_list_t::const_iterator it = inventory->begin(); + LLInventoryObject::object_list_t::const_iterator end = inventory->end(); + for (; it != end; ++it) + { + if ((*it)->getType() != LLAssetType::AT_CATEGORY) + { + LLInventoryObject* obj = (LLInventoryObject*)(*it); + LLInventoryItem* item = (LLInventoryItem*)(obj); + LLUUID item_id; + item_id.generate(); + time_t creation_date_utc = time_corrected(); + LLPointer<LLViewerInventoryItem> new_item + = new LLViewerInventoryItem(item_id, + new_cat_id, + item->getPermissions(), + item->getAssetUUID(), + item->getType(), + item->getInventoryType(), + item->getName(), + item->getDescription(), + LLSaleInfo::DEFAULT, + item->getFlags(), + creation_date_utc); + new_item->updateServer(TRUE); + gInventory.updateItem(new_item); + } + } + gInventory.notifyObservers(); - LLInventoryObject::object_list_t::const_iterator it = inventory->begin(); - LLInventoryObject::object_list_t::const_iterator end = inventory->end(); - for ( ; it != end; ++it) - { - if ((*it)->getType() != LLAssetType::AT_CATEGORY) - { - LLInventoryObject* obj = (LLInventoryObject*)(*it); - LLInventoryItem* item = (LLInventoryItem*)(obj); - LLUUID item_id; - item_id.generate(); - time_t creation_date_utc = time_corrected(); - LLPointer<LLViewerInventoryItem> new_item - = new LLViewerInventoryItem(item_id, - cat_id, - item->getPermissions(), - item->getAssetUUID(), - item->getType(), - item->getInventoryType(), - item->getName(), - item->getDescription(), - LLSaleInfo::DEFAULT, - item->getFlags(), - creation_date_utc); - new_item->updateServer(TRUE); - gInventory.updateItem(new_item); - } - } - gInventory.notifyObservers(); - if(active_panel) - { - active_panel->setSelection(cat_id, TAKE_FOCUS_NO); - } + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if (active_panel) + { + active_panel->setSelection(new_cat_id, TAKE_FOCUS_NO); + } + }); } else if (inventory->size() == 2) { diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index f47f0b4572..9275cfb86d 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -63,7 +63,6 @@ #include "llcontrolavatar.h" #include "lldrawable.h" #include "llface.h" -#include "llfloaterproperties.h" #include "llfloatertools.h" #include "llfollowcam.h" #include "llhudtext.h" @@ -3474,9 +3473,6 @@ void LLViewerObject::doInventoryCallback() void LLViewerObject::removeInventory(const LLUUID& item_id) { - // close any associated floater properties - LLFloaterReg::hideInstance("properties", item_id); - LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveTaskInventory); msg->nextBlockFast(_PREHASH_AgentData); @@ -6038,7 +6034,7 @@ LLViewerObject::ExtraParameter* LLViewerObject::createNewParameterEntry(U16 para } default: { - LL_INFOS() << "Unknown param type." << LL_ENDL; + LL_INFOS_ONCE() << "Unknown param type: " << param_type << LL_ENDL; break; } }; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 4a1dd1b8d6..84956d3b3d 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3027,6 +3027,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("InterestList"); + capabilityNames.append("InventoryThumbnailUpload"); capabilityNames.append("GetDisplayNames"); capabilityNames.append("GetExperiences"); capabilityNames.append("AgentExperiences"); diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index d2701f0aff..c28fed9ca4 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -36,6 +36,7 @@ #include "llfloatersidepanelcontainer.h" #include "llfloaterworldmap.h" #include "llfocusmgr.h" +#include "llinspecttexture.h" #include "llinventorybridge.h" #include "llinventorydefines.h" #include "llinventorymodel.h" @@ -245,6 +246,21 @@ public: } virtual BOOL handleToolTip(S32 x, S32 y, MASK mask ) { + if (mItem->getThumbnailUUID().notNull()) + { + LLSD params; + params["inv_type"] = mItem->getInventoryType(); + params["thumbnail_id"] = mItem->getThumbnailUUID(); + params["asset_id"] = mItem->getAssetUUID(); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(mToolTip) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + + return TRUE; + } + if (!mToolTip.empty()) { LLToolTipMgr::instance().show(mToolTip); diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index b48290776a..ddb7014648 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1295,11 +1295,52 @@ void LLViewerTextureList::decodeAllImages(F32 max_time) << LL_ENDL; } +bool LLViewerTextureList::createUploadFile(LLPointer<LLImageRaw> raw_image, + const std::string& out_filename, + const S32 max_image_dimentions, + const S32 min_image_dimentions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + // make a copy, since convertToUploadFile scales raw image + LLPointer<LLImageRaw> scale_image = new LLImageRaw( + raw_image->getData(), + raw_image->getWidth(), + raw_image->getHeight(), + raw_image->getComponents()); + + LLPointer<LLImageJ2C> compressedImage = LLViewerTextureList::convertToUploadFile(scale_image, max_image_dimentions); + if (compressedImage->getWidth() < min_image_dimentions || compressedImage->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + compressedImage->getWidth(), + compressedImage->getHeight()); + compressedImage->setLastError(reason); + return false; + } + if (compressedImage.isNull()) + { + compressedImage->setLastError("Couldn't convert the image to jpeg2000."); + LL_INFOS() << "Couldn't convert to j2c, file : " << out_filename << LL_ENDL; + return false; + } + if (!compressedImage->save(out_filename)) + { + compressedImage->setLastError("Couldn't create the jpeg2000 image for upload."); + LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; + return false; + } + return true; +} BOOL LLViewerTextureList::createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec, - const S32 max_image_dimentions) + const S32 max_image_dimentions, + const S32 min_image_dimentions, + bool force_square) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // Load the image @@ -1327,8 +1368,18 @@ BOOL LLViewerTextureList::createUploadFile(const std::string& filename, image->setLastError("Image files with less than 3 or more than 4 components are not supported."); return FALSE; } + if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + image->getWidth(), + image->getHeight()); + image->setLastError(reason); + return FALSE; + } // Convert to j2c (JPEG2000) and save the file locally - LLPointer<LLImageJ2C> compressedImage = convertToUploadFile(raw_image, max_image_dimentions); + LLPointer<LLImageJ2C> compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); if (compressedImage.isNull()) { image->setLastError("Couldn't convert the image to jpeg2000."); @@ -1353,10 +1404,20 @@ BOOL LLViewerTextureList::createUploadFile(const std::string& filename, } // note: modifies the argument raw_image!!!! -LLPointer<LLImageJ2C> LLViewerTextureList::convertToUploadFile(LLPointer<LLImageRaw> raw_image, const S32 max_image_dimentions) +LLPointer<LLImageJ2C> LLViewerTextureList::convertToUploadFile(LLPointer<LLImageRaw> raw_image, const S32 max_image_dimentions, bool force_square) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); + if (force_square) + { + S32 biggest_side = llmax(raw_image->getWidth(), raw_image->getHeight()); + S32 square_size = raw_image->biasedDimToPowerOfTwo(biggest_side, max_image_dimentions); + + raw_image->scale(square_size, square_size); + } + else + { + raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); + } LLPointer<LLImageJ2C> compressedImage = new LLImageJ2C(); if (gSavedSettings.getBOOL("LosslessJ2CUpload") && diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 0018e78d45..82dec6b329 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -92,11 +92,19 @@ class LLViewerTextureList friend class LLLocalBitmap; public: + static bool createUploadFile(LLPointer<LLImageRaw> raw_image, + const std::string& out_filename, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + const S32 min_image_dimentions = 0); static BOOL createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec, - const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - static LLPointer<LLImageJ2C> convertToUploadFile(LLPointer<LLImageRaw> raw_image, const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + const S32 min_image_dimentions = 0, + bool force_square = false); + static LLPointer<LLImageJ2C> convertToUploadFile(LLPointer<LLImageRaw> raw_image, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + bool force_square = false); static void processImageNotInDatabase( LLMessageSystem *msg, void **user_data ); public: diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 348442cf18..185e8612bc 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -9455,7 +9455,14 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) // RequestAgentUpdateAppearanceResponder::onRequestRequested() // assumes that cof version is only updated with server-bake // appearance messages. - LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL; + if (isSelf()) + { + LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL; + } + else + { + LL_INFOS("Avatar") << "Processing appearance message for " << getID() << ", version " << thisAppearanceVersion << LL_ENDL; + } // Note: // locally the COF is maintained via LLInventoryModel::accountForUpdate diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 86c6567c83..c0d4dd0181 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5114,8 +5114,6 @@ void LLControlAVBridge::updateSpatialExtents() { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - LLControlAvatar* controlAvatar = getVObj()->getControlAvatar(); - LLSpatialGroup* root = (LLSpatialGroup*)mOctree->getListener(0); bool rootWasDirty = root->isDirty(); @@ -5126,7 +5124,11 @@ void LLControlAVBridge::updateSpatialExtents() // disappear when root goes off-screen" // // Expand extents to include Control Avatar placed outside of the bounds - if (controlAvatar && (rootWasDirty || controlAvatar->mPlaying)) + LLControlAvatar* controlAvatar = getVObj() ? getVObj()->getControlAvatar() : NULL; + if (controlAvatar + && controlAvatar->mDrawable + && controlAvatar->mDrawable->getEntry() + && (rootWasDirty || controlAvatar->mPlaying)) { root->expandExtents(controlAvatar->mDrawable->getSpatialExtents(), *mDrawable->getXform()); } diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index a6820086fc..c977cd3bef 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -407,7 +407,7 @@ value="0.43 0.06 0.06 1" /> <color name="HTMLLinkColor" - reference="EmphasisColor" /> + value="0.3 0.82 1 1" /> <color name="HealthTextColor" reference="White" /> diff --git a/indra/newview/skins/default/textures/icons/copy_clipboard.png b/indra/newview/skins/default/textures/icons/copy_clipboard.png Binary files differnew file mode 100644 index 0000000000..bb1ceff1ce --- /dev/null +++ b/indra/newview/skins/default/textures/icons/copy_clipboard.png diff --git a/indra/newview/skins/default/textures/icons/delete_icon.png b/indra/newview/skins/default/textures/icons/delete_icon.png Binary files differnew file mode 100644 index 0000000000..37ce374653 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/delete_icon.png diff --git a/indra/newview/skins/default/textures/icons/file_upload.png b/indra/newview/skins/default/textures/icons/file_upload.png Binary files differnew file mode 100644 index 0000000000..58f2757136 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/file_upload.png diff --git a/indra/newview/skins/default/textures/icons/multi_folder_mode.png b/indra/newview/skins/default/textures/icons/multi_folder_mode.png Binary files differnew file mode 100644 index 0000000000..8cda3efc36 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/multi_folder_mode.png diff --git a/indra/newview/skins/default/textures/icons/paste_clipboard.png b/indra/newview/skins/default/textures/icons/paste_clipboard.png Binary files differnew file mode 100644 index 0000000000..e1589ab098 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/paste_clipboard.png diff --git a/indra/newview/skins/default/textures/icons/single_folder_back.png b/indra/newview/skins/default/textures/icons/single_folder_back.png Binary files differnew file mode 100644 index 0000000000..b614e9ef9b --- /dev/null +++ b/indra/newview/skins/default/textures/icons/single_folder_back.png diff --git a/indra/newview/skins/default/textures/icons/single_folder_forward.png b/indra/newview/skins/default/textures/icons/single_folder_forward.png Binary files differnew file mode 100644 index 0000000000..c7bee3522d --- /dev/null +++ b/indra/newview/skins/default/textures/icons/single_folder_forward.png diff --git a/indra/newview/skins/default/textures/icons/single_folder_mode.png b/indra/newview/skins/default/textures/icons/single_folder_mode.png Binary files differnew file mode 100644 index 0000000000..f70b754123 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/single_folder_mode.png diff --git a/indra/newview/skins/default/textures/icons/single_folder_up.png b/indra/newview/skins/default/textures/icons/single_folder_up.png Binary files differnew file mode 100644 index 0000000000..651b2b1af1 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/single_folder_up.png diff --git a/indra/newview/skins/default/textures/icons/snapshot_icon.png b/indra/newview/skins/default/textures/icons/snapshot_icon.png Binary files differnew file mode 100644 index 0000000000..41d524678f --- /dev/null +++ b/indra/newview/skins/default/textures/icons/snapshot_icon.png diff --git a/indra/newview/skins/default/textures/icons/texture_icon.png b/indra/newview/skins/default/textures/icons/texture_icon.png Binary files differnew file mode 100644 index 0000000000..278760a5b0 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/texture_icon.png diff --git a/indra/newview/skins/default/textures/icons/thumbnail_fallback_icon.png b/indra/newview/skins/default/textures/icons/thumbnail_fallback_icon.png Binary files differnew file mode 100644 index 0000000000..8d5ca624af --- /dev/null +++ b/indra/newview/skins/default/textures/icons/thumbnail_fallback_icon.png diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index d0fa19f1a1..656c9b146e 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -253,10 +253,16 @@ with the same filename but different name <texture name="Icon_Close_Foreground" file_name="windows/Icon_Close_Foreground.png" preload="true" /> <texture name="Icon_Close_Press" file_name="windows/Icon_Close_Press.png" preload="true" /> <texture name="Icon_Close_Toast" file_name="windows/Icon_Close_Toast.png" preload="true" /> + + <texture name="Icon_Copy" file_name="icons/copy_clipboard.png" preload="true" /> + + <texture name="Icon_Delete" file_name="icons/delete_icon.png" preload="true" /> <texture name="Icon_Dock_Foreground" file_name="windows/Icon_Dock_Foreground.png" preload="true" /> <texture name="Icon_Dock_Press" file_name="windows/Icon_Dock_Press.png" preload="true" /> + <texture name="Icon_File_Upload" file_name="icons/file_upload.png" preload="true" /> + <texture name="Icon_For_Sale" file_name="icons/Icon_For_Sale.png" preload="false" /> <texture name="Icon_Gear_Background" file_name="windows/Icon_Gear_Background.png" preload="false" /> @@ -269,10 +275,16 @@ with the same filename but different name <texture name="Icon_Minimize_Foreground" file_name="windows/Icon_Minimize_Foreground.png" preload="true" /> <texture name="Icon_Minimize_Press" file_name="windows/Icon_Minimize_Press.png" preload="true" /> + + <texture name="Icon_Paste" file_name="icons/paste_clipboard.png" preload="true" /> <texture name="Icon_Restore_Foreground" file_name="windows/Icon_Restore_Foreground.png" preload="false" /> <texture name="Icon_Restore_Press" file_name="windows/Icon_Restore_Press.png" preload="false" /> + <texture name="Icon_Snapshot" file_name="icons/snapshot_icon.png" preload="true" /> + + <texture name="Icon_Use_Texture" file_name="icons/texture_icon.png" preload="true" /> + <texture name="Info" file_name="icons/Info.png" preload="false" /> <texture name="Info_Small" file_name="icons/Info_Small.png" preload="false" /> <texture name="Info_Off" file_name="navbar/Info_Off.png" preload="false" /> @@ -685,6 +697,7 @@ with the same filename but different name <texture name="TextField_Active" file_name="widgets/TextField_Active.png" preload="true" scale.left="9" scale.top="12" scale.right="248" scale.bottom="12" /> <texture name="TextField_Search_Highlight" file_name="widgets/TextField_Search_Highlight.png" preload="true" scale.left="9" scale.top="12" scale.right="248" scale.bottom="12" /> + <texture name="Thumbnail_Fallback" file_name="icons/thumbnail_fallback_icon.png" preload="true" /> <texture name="Toast_CloseBtn" file_name="windows/Toast_CloseBtn.png" preload="true" /> <texture name="Toast_Background" file_name="windows/Toast_Background.png" preload="true" @@ -881,4 +894,10 @@ with the same filename but different name <texture name="System_Notification" file_name="icons/SL_Logo.png" preload="true"/> <texture name="Icon_Attachment_Small" file_name="icons/Icon_Attachment_Small.png" preload="true"/> <texture name="Icon_Attachment_Large" file_name="icons/Icon_Attachment_Large.png" preload="true"/> + + <texture name="Single_Folder_Mode" file_name="icons/single_folder_mode.png" preload="true"/> + <texture name="Multi_Folder_Mode" file_name="icons/multi_folder_mode.png" preload="true"/> + <texture name="Single_Folder_Back" file_name="icons/single_folder_back.png" preload="true"/> + <texture name="Single_Folder_Forward" file_name="icons/single_folder_forward.png" preload="true"/> + <texture name="Single_Folder_Up" file_name="icons/single_folder_up.png" preload="true"/> </textures> diff --git a/indra/newview/skins/default/xui/da/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/da/floater_inventory_item_properties.xml deleted file mode 100644 index 59dcc87140..0000000000 --- a/indra/newview/skins/default/xui/da/floater_inventory_item_properties.xml +++ /dev/null @@ -1,67 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="OPLYSNINGER OM BEHOLDNINGSGENSTAND"> - <floater.string name="unknown"> - (ukendt) - </floater.string> - <floater.string name="public"> - (offentlig) - </floater.string> - <floater.string name="you_can"> - Du kan: - </floater.string> - <floater.string name="owner_can"> - Ejer kan: - </floater.string> - <floater.string name="acquiredDate"> - [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local] - </floater.string> - <text name="LabelItemNameTitle"> - Navn: - </text> - <text name="LabelItemDescTitle"> - Beskrivelse: - </text> - <text name="LabelCreatorTitle"> - Skaber: - </text> - <button label="Profil..." label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle"> - Ejer: - </text> - <button label="Profil..." label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle"> - Erhvervet: - </text> - <text name="LabelAcquiredDate"> - Wed May 24 12:50:46 2006 - </text> - <text name="OwnerLabel"> - Dig: - </text> - <check_box label="Redigér" name="CheckOwnerModify"/> - <check_box label="Kopiere" name="CheckOwnerCopy"/> - <check_box label="Sælg" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel"> - Enhver: - </text> - <check_box label="Kopiér" name="CheckEveryoneCopy"/> - <text name="GroupLabel"> - Gruppe: - </text> - <check_box label="Del" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel"> - Næste ejer: - </text> - <check_box label="Redigér" name="CheckNextOwnerModify"/> - <check_box label="Kopiere" name="CheckNextOwnerCopy"/> - <check_box label="Sælg" name="CheckNextOwnerTransfer"/> - <check_box label="Til salg" name="CheckPurchase"/> - <combo_box name="combobox sale copy"> - <combo_box.item label="Kopiér" name="Copy"/> - <combo_box.item label="Original" name="Original"/> - </combo_box> - <spinner label="Pris:" name="Edit Cost"/> - <text name="CurrencySymbol"> - L$ - </text> -</floater> diff --git a/indra/newview/skins/default/xui/de/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/de/floater_inventory_item_properties.xml deleted file mode 100644 index 92c038057f..0000000000 --- a/indra/newview/skins/default/xui/de/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="EIGENSCHAFTEN: INVENTAROBJEKT"> - <floater.string name="unknown">(unbekannt)</floater.string> - <floater.string name="public">(öffentlich)</floater.string> - <floater.string name="you_can">Sie können:</floater.string> - <floater.string name="owner_can">Eigentümer kann:</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Name:</text> - <text name="LabelItemDescTitle">Beschreibung:</text> - <text name="LabelCreatorTitle">Ersteller:</text> - <button label="Profil..." label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle">Eigentümer:</text> - <button label="Profil..." label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle">Erworben:</text> - <text name="LabelAcquiredDate">Mittwoch, 24. Mai 2006, 12:50:46</text> - <text name="OwnerLabel">Sie:</text> - <check_box label="Bearbeiten" name="CheckOwnerModify"/> - <check_box label="Kopieren" left_delta="85" name="CheckOwnerCopy"/> - <check_box label="Wiederverkaufen" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">Jeder:</text> - <check_box label="Kopieren" name="CheckEveryoneCopy"/> - <text name="GroupLabel">Gruppe:</text> - <check_box label="Teilen" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel" width="150">Nächster Eigentümer:</text> - <check_box label="Bearbeiten" name="CheckNextOwnerModify"/> - <check_box label="Kopieren" left_delta="55" name="CheckNextOwnerCopy"/> - <check_box label="Wiederverkaufen" name="CheckNextOwnerTransfer"/> - <check_box label="Zum Verkauf" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Kopie" name="Copy"/> - <combo_box.item label="Inhalt" name="Contents"/> - <combo_box.item label="Original" name="Original"/> - </combo_box> - <spinner label="Preis:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/de/panel_main_inventory.xml b/indra/newview/skins/default/xui/de/panel_main_inventory.xml index a3adea9fa2..175f6e1003 100644 --- a/indra/newview/skins/default/xui/de/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/de/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> Geholte [ITEM_COUNT] Bestellungen und [CATEGORY_COUNT] Ordner [FILTER] </panel.string> + <panel.string name="inventory_title">INVENTAR</panel.string> <text name="ItemcountText"> Objekte: </text> diff --git a/indra/newview/skins/default/xui/en/floater_change_item_thumbnail.xml b/indra/newview/skins/default/xui/en/floater_change_item_thumbnail.xml new file mode 100644 index 0000000000..726cb38481 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_change_item_thumbnail.xml @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + can_resize="false" + height="366" + layout="topleft" + name="change_item_thumbnail" + help_topic="change_item_thumbnail" + title="CHANGE ITEM IMAGE" + width="319"> + + <floater.string + name="title_item_thumbnail"> + CHANGE ITEM IMAGE + </floater.string> + <floater.string + name="title_folder_thumbnail"> + CHANGE FOLDER IMAGE + </floater.string> + <floater.string + name="tooltip_upload_local"> + Upload from computer + </floater.string> + <floater.string + name="tooltip_upload_snapshot"> + Use snapshot tool + </floater.string> + <floater.string + name="tooltip_use_texture"> + Choose texture + </floater.string> + <floater.string + name="tooltip_copy_to_clipboard"> + Copy to clipboard + </floater.string> + <floater.string + name="tooltip_paste_from_clipboard"> + Paste from clipboard + </floater.string> + <floater.string + name="tooltip_remove_image"> + Remove image + </floater.string> + + <icon + follows="top|left" + height="16" + image_name="Inv_Object" + layout="topleft" + left="10" + mouse_opaque="true" + name="item_type_icon" + top="4" + width="16" /> + <text + name="item_name" + font="SansSerif" + use_ellipses="true" + follows="left|top|right" + layout="topleft" + height="19" + top_delta="1" + left_pad="3" + width="286"/> + + <thumbnail + name="item_thumbnail" + fallback_image="Thumbnail_Fallback" + follows="top|left" + layout="topleft" + left="32" + top_pad="9" + height="256" + width="256" + /> + + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_File_Upload" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="upload_local" + left="38" + top_pad="9" + height="32" + width="32" /> + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_Snapshot" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="upload_snapshot" + left_pad="10" + top_delta="0" + height="32" + width="32" /> + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_Use_Texture" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="use_texture" + left_pad="10" + top_delta="0" + height="32" + width="32" /> + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_Copy" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="copy_to_clipboard" + left_pad="10" + top_delta="0" + height="32" + width="32" /> + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_Paste" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="paste_from_clipboard" + left_pad="10" + top_delta="0" + height="32" + width="32" /> + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_Delete" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="remove_image" + left_pad="10" + top_delta="0" + height="32" + width="32" /> + + <text + type="string" + halign="center" + length="1" + follows="left|top" + height="17" + layout="topleft" + left="5" + right="-5" + name="tooltip_text" + top_pad="12" + width="78"> + tooltip + </text> + +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_inventory_settings.xml b/indra/newview/skins/default/xui/en/floater_inventory_settings.xml new file mode 100644 index 0000000000..2e619d91fb --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_inventory_settings.xml @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + can_close="true" + can_minimize="true" + can_resize="false" + save_rect="true" + height="370" + width="370" + name="inventory_settings" + title="INVENTORY SETTINGS"> + <icon + follows="top|left" + height="18" + image_name="Multi_Folder_Mode" + layout="topleft" + left="18" + mouse_opaque="true" + name="multi_folder_icon" + top="25" + width="18" /> + <text + type="string" + length="1" + follows="left|top|right" + height="13" + layout="topleft" + left_pad="12" + top_delta="2" + name="multi_folder_txt" + font="SansSerifMedium" + text_color="White" + width="300"> + Double-click on folder in multi-folder view: + </text> + <radio_group + control_name="MultiModeDoubleClickFolder" + follows="left|top" + top_pad="8" + layout="topleft" + font="SansSerifMedium" + left="60" + width="300" + height="70" + name="multi_double_click_setting"> + <radio_item + height="20" + label="Expands & collapses folder" + label_text.text_color="White" + follows="left|top" + layout="topleft" + name="0" + width="200"/> + <radio_item + height="20" + follows="left|top" + label="Opens a new window" + label_text.text_color="White" + layout="topleft" + left_delta="0" + name="1" + top_pad ="5" + width="200" /> + <radio_item + height="20" + follows="left|top" + label="Switches view" + label_text.text_color="White" + layout="topleft" + left_delta="0" + name="2" + top_pad ="5" + width="200" /> + </radio_group> + <icon + follows="top|left" + height="18" + image_name="Single_Folder_Mode" + layout="topleft" + left="18" + mouse_opaque="true" + name="single_folder_icon" + top_pad="30" + width="18" /> + <text + type="string" + length="1" + follows="left|top|right" + height="13" + layout="topleft" + left_pad="12" + top_delta="2" + name="single_folder_txt" + font="SansSerifMedium" + text_color="White" + width="300"> + Double-click on folder in single-folder view: + </text> + <radio_group + control_name="SingleModeDoubleClickOpenWindow" + follows="left|top" + top_pad="8" + layout="topleft" + font="SansSerifMedium" + left="60" + width="300" + height="45" + name="single_double_click_setting"> + <radio_item + height="20" + label="Stays in current window" + label_text.text_color="White" + follows="left|top" + layout="topleft" + name="false" + width="200"/> + <radio_item + height="20" + follows="left|top" + label="Opens a new window" + label_text.text_color="White" + layout="topleft" + left_delta="0" + name="true" + top_pad ="5" + width="200" /> + </radio_group> + <text + type="string" + length="1" + follows="left|top|right" + height="13" + layout="topleft" + left="48" + name="find_original_txt" + font="SansSerifMedium" + text_color="White" + top_pad="30" + width="300"> + Clicking on "Show in inventory" or "Find original" + </text> + <radio_group + control_name="FindOriginalOpenWindow" + follows="left|top" + top_pad="8" + layout="topleft" + font="SansSerifMedium" + left="60" + width="300" + height="45" + name="find_original_settings"> + <radio_item + height="20" + label="Shows item in main inventory window" + label_text.text_color="White" + follows="left|top" + layout="topleft" + name="false" + width="200"/> + <radio_item + height="20" + follows="left|top" + label="Opens a new single-folder window" + label_text.text_color="White" + layout="topleft" + left_delta="0" + name="true" + top_pad ="5" + width="200" /> + </radio_group> + <button + height="20" + label="OK" + layout="topleft" + left="140" + bottom="-20" + name="ok_btn" + label_color="White" + width="90" /> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_item_properties.xml b/indra/newview/skins/default/xui/en/floater_item_properties.xml index 0fc54a9c8b..336bb902ca 100644 --- a/indra/newview/skins/default/xui/en/floater_item_properties.xml +++ b/indra/newview/skins/default/xui/en/floater_item_properties.xml @@ -13,7 +13,7 @@ left="0" class="sidepanel_item_info" filename="sidepanel_item_info.xml" - name="item_panel" + name="sidepanel" top="20" label="" height="570" diff --git a/indra/newview/skins/default/xui/en/floater_my_inventory.xml b/indra/newview/skins/default/xui/en/floater_my_inventory.xml index f182d27da8..a9900f05b7 100644 --- a/indra/newview/skins/default/xui/en/floater_my_inventory.xml +++ b/indra/newview/skins/default/xui/en/floater_my_inventory.xml @@ -5,14 +5,14 @@ can_resize="true" height="570" help_topic="sidebar_inventory" - min_width="333" - min_height="590" + min_width="363" + min_height="270" name="floater_my_inventory" save_rect="true" save_visibility="true" reuse_instance="true" title="INVENTORY" - width="333" > + width="363" > <panel class="sidepanel_inventory" name="main_panel" diff --git a/indra/newview/skins/default/xui/en/floater_new_feature_notification.xml b/indra/newview/skins/default/xui/en/floater_new_feature_notification.xml new file mode 100644 index 0000000000..5f0eeab71c --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_new_feature_notification.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + height="130" + width="300" + layout="topleft" + name="floater_new_feature_notification" + title="NEW FEATURE" + show_title="false" + header_height="0" + bg_opaque_image="Window_NoTitle_Foreground" + bg_alpha_image="Window_NoTitle_Background" + can_resize="false" + can_drag_on_left="false" + can_minimize="false" + can_close="false"> + <floater.string name="title_txt_inventory"> +New inventory features + </floater.string> + <floater.string name="description_txt_inventory"> +You can now add preview images to inventory items and view a folder in its own window. +Learn more in this [https://community.secondlife.com/blogs/entry/13637-new-features-inventory-item-preview-and-single-folder-view/ blogpost] + </floater.string> + <text + type="string" + length="1" + follows="top|left|right" + font="SansSerifLargeBold" + text_color="White" + layout="topleft" + left="10" + height="14" + top="10" + right="-10" + name="title_txt"> +New feature + </text> + <text + type="string" + length="1" + follows="top|left|right" + text_color="White" + layout="topleft" + left="10" + height="40" + top_pad="14" + right="-10" + word_wrap="true" + name="description_txt"> +Feature description + </text> + <button + follows="bottom|left|right" + layout="topleft" + height="24" + label="Got it!" + left="104" + bottom="-10" + name="close_btn" + width="90"/> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_outfit_photo_preview.xml b/indra/newview/skins/default/xui/en/floater_outfit_photo_preview.xml deleted file mode 100644 index bfc1c39e9d..0000000000 --- a/indra/newview/skins/default/xui/en/floater_outfit_photo_preview.xml +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> -<floater - legacy_header_height="18" - can_resize="false" - height="325" - layout="topleft" - name="outfit_photo_preview" - help_topic="preview_texture" - width="410"> - <floater.string - name="Title"> - Texture: [NAME] - </floater.string> - <floater.string - name="exceed_limits"> - Max outfit photo size is [MAX_WIDTH]*[MAX_HEIGHT]. Please select another texture. - </floater.string> - <floater.string - name="photo_confirmation"> - Set this as Outfit Photo for [OUTFIT]? - </floater.string> - <text - type="string" - halign="right" - length="1" - follows="right|bottom" - height="16" - layout="topleft" - left="110" - name="dimensions" - top="255" - width="200"> - [WIDTH]px x [HEIGHT]px - </text> - <text - type="string" - follows="left|top" - height="16" - layout="topleft" - name="notification" - left="25" - halign="center" - top_pad="5" - width="360"> - </text> - <button - follows="right|bottom" - height="22" - label="OK" - layout="topleft" - name="ok_btn" - top_pad="5" - right="-115" - top_delta="0" - width="90" /> - <button - follows="right|bottom" - height="22" - label="Cancel" - layout="topleft" - name="cancel_btn" - right="-20" - top_delta="0" - width="90" /> -</floater> diff --git a/indra/newview/skins/default/xui/en/floater_simple_outfit_snapshot.xml b/indra/newview/skins/default/xui/en/floater_simple_snapshot.xml index 5ece7b85d5..484ad159d1 100644 --- a/indra/newview/skins/default/xui/en/floater_simple_outfit_snapshot.xml +++ b/indra/newview/skins/default/xui/en/floater_simple_snapshot.xml @@ -2,17 +2,17 @@ <floater positioning="cascading" legacy_header_height="18" - can_minimize="true" + can_minimize="false" can_resize="false" can_close="true" height="305" layout="topleft" - name="simple_outfit_snapshot" - single_instance="true" + name="simple_snapshot" + single_instance="false" help_topic="snapshot" save_rect="true" save_visibility="false" - title="OUTFIT SNAPSHOT" + title="ITEM SNAPSHOT" width="351"> <ui_ctrl layout="topleft" @@ -36,7 +36,7 @@ height="22" layout="topleft" left_pad="10" - label="Save (L$[UPLOAD_COST])" + label="Save" name="save_btn" width="90" /> <button diff --git a/indra/newview/skins/default/xui/en/floater_snapshot.xml b/indra/newview/skins/default/xui/en/floater_snapshot.xml index f441e3cbd7..fcd24d83bb 100644 --- a/indra/newview/skins/default/xui/en/floater_snapshot.xml +++ b/indra/newview/skins/default/xui/en/floater_snapshot.xml @@ -440,7 +440,7 @@ length="1" halign="right" name="360_label" - text_color="0.3 0.82 1 1" + text_color="HTMLLinkColor" top_delta="0" type="string" width="115"> diff --git a/indra/newview/skins/default/xui/en/floater_task_properties.xml b/indra/newview/skins/default/xui/en/floater_task_properties.xml new file mode 100644 index 0000000000..56c236eab4 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_task_properties.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="590" + layout="topleft" + name="Task Properties" + help_topic="item+properties" + title="ITEM PROPERTIES" + single_instance="true" + width="330"> + <panel + follows="all" + layout="topleft" + left="0" + class="sidepanel_task_info" + filename="sidepanel_task_info.xml" + name="sidepanel" + top="20" + label="" + height="570" + visible="true" + width="330"> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml b/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml index 3a66911389..8081af6673 100644 --- a/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml +++ b/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml @@ -83,6 +83,22 @@ top_pad="4"> [DIMENSIONS] </text> + <text + type="string" + text_color="Yellow" + length="1" + word_wrap="true" + follows="left|top" + height="56" + width="164" + layout="topleft" + left="8" + name="over_limit_lbl" + visible="false" + top_delta="0"> + Selected texture is [TEXDIM]. Inventory image must be square, no less than [MINTEXDIM]. + </text> + <!-- middle: inventory mode --> <button diff --git a/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml b/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml new file mode 100644 index 0000000000..d82c453e5f --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_gallery_inventory.xml @@ -0,0 +1,515 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<context_menu + layout="topleft" + name="Gallery"> + <menu_item_call + label="Share" + layout="topleft" + name="Share"> + <menu_item_call.on_click + function="Inventory.Share" /> + </menu_item_call> + <menu_item_call + label="Empty Trash" + layout="topleft" + name="Empty Trash"> + <menu_item_call.on_click + function="Inventory.EmptyTrash"/> + </menu_item_call> + <menu_item_call + label="Empty Lost And Found" + layout="topleft" + name="Empty Lost And Found"> + <menu_item_call.on_click + function="Inventory.EmptyLostAndFound"/> + </menu_item_call> + <menu_item_call + label="Teleport" + layout="topleft" + name="Landmark Open"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Folder Wearables Separator" /> + <menu_item_call + label="Replace Current Outfit" + layout="topleft" + name="Replace Outfit"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="replaceoutfit" /> + </menu_item_call> + <menu_item_call + label="Add To Current Outfit" + layout="topleft" + name="Add To Outfit"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="addtooutfit" /> + </menu_item_call> + <menu_item_call + label="Remove From Current Outfit" + layout="topleft" + name="Remove From Outfit"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="removefromoutfit" /> + </menu_item_call> + <menu_item_call + label="Copy outfit list to clipboard" + layout="topleft" + name="Copy outfit list to clipboard"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="copyoutfittoclipboard" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Outfit Separator" /> + <menu_item_call + label="Find Original" + layout="topleft" + name="Find Original"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="goto" /> + </menu_item_call> + <menu_item_call + label="Purge Item" + layout="topleft" + name="Purge Item"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="purge"/> + </menu_item_call> + <menu_item_call + label="Restore Item" + layout="topleft" + name="Restore Item"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="restore" /> + </menu_item_call> + <menu_item_call + label="Open" + layout="topleft" + name="Open"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open" /> + </menu_item_call> + <menu_item_call + label="Open Original" + layout="topleft" + name="Open Original"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open_original" /> + </menu_item_call> + <menu_item_call + label="Properties" + layout="topleft" + name="Properties"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="properties" /> + </menu_item_call> + <menu_item_call + label="Image..." + layout="topleft" + name="thumbnail"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="thumbnail" /> + </menu_item_call> + <menu_item_call + label="Copy Asset UUID" + layout="topleft" + name="Copy Asset UUID"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="copy_uuid" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Copy Separator" /> + <menu_item_call + label="Open" + layout="topleft" + name="open_in_current_window"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open_selected_folder" /> + </menu_item_call> + <menu_item_call + label="Open in new window" + layout="topleft" + name="open_in_new_window"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open_in_new_window" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Open Folder Separator" /> + <menu_item_call + label="Rename" + layout="topleft" + name="Rename"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="rename" /> + </menu_item_call> + <menu_item_call + label="Cut" + layout="topleft" + name="Cut"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="cut" /> + </menu_item_call> + <menu_item_call + label="Copy" + layout="topleft" + name="Copy"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="copy" /> + </menu_item_call> + <menu_item_call + label="Paste" + layout="topleft" + name="Paste"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="paste" /> + </menu_item_call> + <menu_item_call + label="Paste As Link" + layout="topleft" + name="Paste As Link"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="paste_link" /> + </menu_item_call> + <menu_item_call + label="Replace Links" + layout="topleft" + name="Replace Links"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="replace_links" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Paste Separator" /> + <menu_item_call + label="Delete" + layout="topleft" + name="Delete"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="delete" /> + </menu_item_call> + <menu_item_call + label="Delete System Folder" + layout="topleft" + name="Delete System Folder"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="delete_system_folder" /> + </menu_item_call> + <menu_item_separator + layout="topleft" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + label="Play" + layout="topleft" + name="Sound Play"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="sound_play" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Landmark Separator" /> + <menu_item_call + label="Copy SLurl" + layout="topleft" + name="url_copy"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="copy_slurl" /> + </menu_item_call> + <menu_item_call + label="About Landmark" + layout="topleft" + name="About Landmark"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="about" /> + </menu_item_call> + <menu_item_call + label="Show on Map" + layout="topleft" + name="show_on_map"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="show_on_map" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Animation Separator" /> + <menu_item_call + label="Play Inworld" + layout="topleft" + name="Animation Play"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="playworld" /> + </menu_item_call> + <menu_item_call + label="Play Locally" + layout="topleft" + name="Animation Audition"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="playlocal" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Send Instant Message Separator" /> + <menu_item_call + label="Send Instant Message" + layout="topleft" + name="Send Instant Message"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="begin_im" /> + </menu_item_call> + <menu_item_call + label="Offer Teleport..." + layout="topleft" + name="Offer Teleport..."> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="lure" /> + </menu_item_call> + <menu_item_call + label="Request Teleport..." + layout="topleft" + name="Request Teleport..."> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="request_lure" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Gesture Separator" /> + <menu_item_call + label="Activate" + layout="topleft" + name="Activate"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="activate" /> + </menu_item_call> + <menu_item_call + label="Deactivate" + layout="topleft" + name="Deactivate"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="deactivate" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Texture Separator" /> + <menu_item_call + label="Save As" + layout="topleft" + name="Save As"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="save_as" /> + </menu_item_call> + <menu_item_call + label="Save Selected As" + layout="topleft" + name="Save Selected As"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="save_selected_as" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Wearable And Object Separator"/> + <menu_item_call + label="Wear" + layout="topleft" + name="Wearable And Object Wear"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="wear" /> + </menu_item_call> + <menu + label="Attach To" + layout="topleft" + name="Attach To" /> + <menu + label="Attach To HUD" + layout="topleft" + name="Attach To HUD" /> + <menu_item_call + label="Touch" + layout="topleft" + name="Attachment Touch"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="touch" /> + </menu_item_call> + <menu_item_call + label="Edit" + layout="topleft" + name="Wearable Edit"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="edit" /> + </menu_item_call> + <menu_item_call + label="Add" + layout="topleft" + name="Wearable Add"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="wear_add" /> + </menu_item_call> + <menu_item_call + label="Detach From Yourself" + layout="topleft" + name="Detach From Yourself"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="detach" /> + </menu_item_call> + <menu_item_call + label="Take Off" + layout="topleft" + name="Take Off"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="take_off" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Settings Separator" /> + <menu_item_call + name="Settings Apply Local" + layout="topleft" + label="Apply Only To Myself"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="apply_settings_local" /> + </menu_item_call> + <menu_item_call + name="Settings Apply Parcel" + layout="topleft" + label="Apply To Parcel"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="apply_settings_parcel" /> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Subfolder Separator" /> + <menu_item_call + label="Create folder from selected" + layout="topleft" + name="New folder from selected"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="new_folder_from_selected" /> + </menu_item_call> + <menu_item_call + label="Ungroup folder items" + layout="topleft" + name="Ungroup folder items"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="ungroup_folder_items" /> + </menu_item_call> + <menu + label="Use as default for" + layout="topleft" + name="upload_def"> + <menu_item_call + label="Image uploads" + layout="topleft" + name="Image uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="texture" /> + <menu_item_call.on_visible + function="Inventory.CanSetUploadLocation" /> + </menu_item_call> + <menu_item_call + label="Sound uploads" + layout="topleft" + name="Sound uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="sound" /> + <menu_item_call.on_visible + function="Inventory.CanSetUploadLocation" /> + </menu_item_call> + <menu_item_call + label="Animation uploads" + layout="topleft" + name="Animation uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="animation" /> + <menu_item_call.on_visible + function="Inventory.CanSetUploadLocation" /> + </menu_item_call> + <menu_item_call + label="Model uploads" + layout="topleft" + name="Model uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="model" /> + <menu_item_call.on_visible + function="Inventory.CanSetUploadLocation" /> + </menu_item_call> + </menu> + <menu_item_separator + layout="topleft" + name="Marketplace Separator" /> + <menu_item_call + label="Copy to Marketplace Listings" + layout="topleft" + name="Marketplace Copy"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="copy_to_marketplace_listings" /> + </menu_item_call> + <menu_item_call + label="Move to Marketplace Listings" + layout="topleft" + name="Marketplace Move"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="move_to_marketplace_listings" /> + </menu_item_call> + <menu_item_call + label="--no options--" + layout="topleft" + name="--no options--" /> + <menu_item_separator + layout="topleft" /> +</context_menu> diff --git a/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml b/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml index 99ca910062..0ca505dd5d 100755 --- a/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml +++ b/indra/newview/skins/default/xui/en/menu_gallery_outfit_tab.xml @@ -42,35 +42,11 @@ parameter="take_off" /> </menu_item_call> <menu_item_call - label="Upload Photo (L$[UPLOAD_COST])" - layout="topleft" - name="upload_photo"> - <on_click - function="Outfit.UploadPhoto" /> - </menu_item_call> - <menu_item_call - label="Select Photo" - layout="topleft" - name="select_photo"> + label="Image..." + layout="topleft" + name="thumbnail"> <on_click - function="Outfit.SelectPhoto" /> - </menu_item_call> - <menu_item_call - label="Take a Snapshot" - layout="topleft" - name="take_snapshot"> - <on_click - function="Outfit.TakeSnapshot" /> - </menu_item_call> - <menu_item_call - label="Remove Photo" - layout="topleft" - name="remove_photo"> - <on_click - function="Outfit.RemovePhoto" /> - <on_visible - function="Outfit.OnVisible" - parameter="remove_photo" /> + function="Outfit.Thumbnail" /> </menu_item_call> <menu_item_separator name="sepatator1" /> <menu diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index aa3d0ae071..e650c10603 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -159,248 +159,6 @@ function="Inventory.DoCreate" parameter="outfit" /> </menu_item_call> - <menu_item_call - label="New Script" - layout="topleft" - name="New Script"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="lsl" /> - </menu_item_call> - <menu_item_call - label="New Notecard" - layout="topleft" - name="New Note"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="notecard" /> - </menu_item_call> - <menu_item_call - label="New Gesture" - layout="topleft" - name="New Gesture"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="gesture" /> - </menu_item_call> - <menu - label="New Clothes" - layout="topleft" - name="New Clothes"> - <menu_item_call - label="New Shirt" - layout="topleft" - name="New Shirt"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="shirt" /> - </menu_item_call> - <menu_item_call - label="New Pants" - layout="topleft" - name="New Pants"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="pants" /> - </menu_item_call> - <menu_item_call - label="New Shoes" - layout="topleft" - name="New Shoes"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="shoes" /> - </menu_item_call> - <menu_item_call - label="New Socks" - layout="topleft" - name="New Socks"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="socks" /> - </menu_item_call> - <menu_item_call - label="New Jacket" - layout="topleft" - name="New Jacket"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="jacket" /> - </menu_item_call> - <menu_item_call - label="New Skirt" - layout="topleft" - name="New Skirt"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="skirt" /> - </menu_item_call> - <menu_item_call - label="New Gloves" - layout="topleft" - name="New Gloves"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="gloves" /> - </menu_item_call> - <menu_item_call - label="New Undershirt" - layout="topleft" - name="New Undershirt"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="undershirt" /> - </menu_item_call> - <menu_item_call - label="New Underpants" - layout="topleft" - name="New Underpants"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="underpants" /> - </menu_item_call> - <menu_item_call - label="New Alpha Mask" - layout="topleft" - name="New Alpha Mask"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="alpha" /> - </menu_item_call> - <menu_item_call - label="New Tattoo" - layout="topleft" - name="New Tattoo"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="tattoo" /> - </menu_item_call> - <menu_item_call - label="New Universal" - layout="topleft" - name="New Universal"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="universal" /> - </menu_item_call> - <menu_item_call - label="New Physics" - layout="topleft" - name="New Physics"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="physics" /> - </menu_item_call> - </menu> - <menu - label="New Body Parts" - layout="topleft" - name="New Body Parts"> - <menu_item_call - label="New Shape" - layout="topleft" - name="New Shape"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="shape" /> - </menu_item_call> - <menu_item_call - label="New Skin" - layout="topleft" - name="New Skin"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="skin" /> - </menu_item_call> - <menu_item_call - label="New Hair" - layout="topleft" - name="New Hair"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="hair" /> - </menu_item_call> - <menu_item_call - label="New Eyes" - layout="topleft" - name="New Eyes"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="eyes" /> - </menu_item_call> - </menu> - <menu - label="New Settings" - layout="topleft" - name="New Settings"> - <menu_item_call - label="New Sky" - layout="topleft" - name="New Sky"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="sky"/> - <menu_item_call.on_enable - function="Inventory.EnvironmentEnabled" /> - </menu_item_call> - <menu_item_call - label="New Water" - layout="topleft" - name="New Water"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="water"/> - <menu_item_call.on_enable - function="Inventory.EnvironmentEnabled" /> - </menu_item_call> - <menu_item_call - label="New Day Cycle" - layout="topleft" - name="New Day Cycle"> - <menu_item_call.on_click - function="Inventory.DoCreate" - parameter="daycycle"/> - <menu_item_call.on_enable - function="Inventory.EnvironmentEnabled" /> - </menu_item_call> - </menu> - <menu - label="Use as default for" - layout="topleft" - name="upload_def"> - <menu_item_call - label="Image uploads" - layout="topleft" - name="Image uploads"> - <menu_item_call.on_click - function="Inventory.FileUploadLocation" - parameter="texture" /> - </menu_item_call> - <menu_item_call - label="Sound uploads" - layout="topleft" - name="Sound uploads"> - <menu_item_call.on_click - function="Inventory.FileUploadLocation" - parameter="sound" /> - </menu_item_call> - <menu_item_call - label="Animation uploads" - layout="topleft" - name="Animation uploads"> - <menu_item_call.on_click - function="Inventory.FileUploadLocation" - parameter="animation" /> - </menu_item_call> - <menu_item_call - label="Model uploads" - layout="topleft" - name="Model uploads"> - <menu_item_call.on_click - function="Inventory.FileUploadLocation" - parameter="model" /> - </menu_item_call> - </menu> <menu label="Change Type" layout="topleft" @@ -597,12 +355,12 @@ parameter="properties" /> </menu_item_call> <menu_item_call - label="Rename" + label="Image..." layout="topleft" - name="Rename"> + name="thumbnail"> <menu_item_call.on_click function="Inventory.DoToSelected" - parameter="rename" /> + parameter="thumbnail" /> </menu_item_call> <menu_item_call label="Copy Asset UUID" @@ -624,6 +382,32 @@ layout="topleft" name="Copy Separator" /> <menu_item_call + label="Open" + layout="topleft" + name="open_in_current_window"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open_in_current_window" /> + </menu_item_call> + <menu_item_call + label="Open in new window" + layout="topleft" + name="open_in_new_window"> + <menu_item_call.on_click + function="Inventory.OpenNewFolderWindow"/> + </menu_item_call> + <menu_item_separator + layout="topleft" + name="Open Folder Separator" /> + <menu_item_call + label="Rename" + layout="topleft" + name="Rename"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="rename" /> + </menu_item_call> + <menu_item_call label="Cut" layout="topleft" name="Cut"> @@ -931,6 +715,43 @@ function="Inventory.DoToSelected" parameter="ungroup_folder_items" /> </menu_item_call> + <menu + label="Use as default for" + layout="topleft" + name="upload_def"> + <menu_item_call + label="Image uploads" + layout="topleft" + name="Image uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="texture" /> + </menu_item_call> + <menu_item_call + label="Sound uploads" + layout="topleft" + name="Sound uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="sound" /> + </menu_item_call> + <menu_item_call + label="Animation uploads" + layout="topleft" + name="Animation uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="animation" /> + </menu_item_call> + <menu_item_call + label="Model uploads" + layout="topleft" + name="Model uploads"> + <menu_item_call.on_click + function="Inventory.FileUploadLocation" + parameter="model" /> + </menu_item_call> + </menu> <menu_item_separator layout="topleft" name="Marketplace Separator" /> diff --git a/indra/newview/skins/default/xui/en/menu_inventory_add.xml b/indra/newview/skins/default/xui/en/menu_inventory_add.xml index 3385a29a6c..12ba121d3c 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory_add.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory_add.xml @@ -290,4 +290,13 @@ function="Inventory.EnvironmentEnabled" /> </menu_item_call> </menu> -</menu>
\ No newline at end of file + <menu_item_separator/> + <menu_item_call + label="Shop..." + layout="topleft" + name="Shop"> + <menu_item_call.on_click + function="Inventory.GearDefault.Custom.Action" + parameter="shop" /> + </menu_item_call> +</menu> diff --git a/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml b/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml index 3eacdbc781..2c630880c2 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml @@ -15,52 +15,25 @@ function="Inventory.GearDefault.Custom.Action" parameter="new_window" /> </menu_item_call> - <menu_item_separator - layout="topleft" /> - <menu_item_check - label="Sort by Name" - layout="topleft" - name="sort_by_name"> - <on_click - function="Inventory.GearDefault.Custom.Action" - parameter="sort_by_name" /> - <on_check - function="Inventory.GearDefault.Check" - parameter="sort_by_name" /> - </menu_item_check> - <menu_item_check - label="Sort by Most Recent" - layout="topleft" - name="sort_by_recent"> - <on_click - function="Inventory.GearDefault.Custom.Action" - parameter="sort_by_recent" /> - <on_check - function="Inventory.GearDefault.Check" - parameter="sort_by_recent" /> - </menu_item_check> - <menu_item_check - label="Sort Folders Always by Name" + <menu_item_call + label="Collapse All Folders" layout="topleft" - name="sort_folders_by_name"> + name="close_folders"> <on_click function="Inventory.GearDefault.Custom.Action" - parameter="sort_folders_by_name" /> - <on_check - function="Inventory.GearDefault.Check" - parameter="sort_folders_by_name" /> - </menu_item_check> - <menu_item_check - label="Sort System Folders to Top" + parameter="close_folders" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="multi_folder_view" /> + </menu_item_call> + <menu_item_call + label="Close All Windows" layout="topleft" - name="sort_system_folders_to_top"> + name="close_windows"> <on_click function="Inventory.GearDefault.Custom.Action" - parameter="sort_system_folders_to_top" /> - <on_check - function="Inventory.GearDefault.Check" - parameter="sort_system_folders_to_top" /> - </menu_item_check> + parameter="close_inv_windows" /> + </menu_item_call> <menu_item_separator layout="topleft" /> <menu_item_call @@ -79,29 +52,6 @@ function="Inventory.GearDefault.Custom.Action" parameter="reset_filters" /> </menu_item_call> - <menu_item_call - label="Close All Folders" - layout="topleft" - name="close_folders"> - <on_click - function="Inventory.GearDefault.Custom.Action" - parameter="close_folders" /> - </menu_item_call> - <menu_item_separator - layout="topleft" /> - <menu_item_call - label="Empty Lost and Found" - layout="topleft" - name="empty_lostnfound"> - <on_click - function="Inventory.GearDefault.Custom.Action" - parameter="empty_lostnfound" /> - <on_enable - function="Inventory.GearDefault.Enable" - parameter="empty_lostnfound" /> - </menu_item_call> - <menu_item_separator - layout="topleft" /> <menu_item_call label="Save Texture As" layout="topleft" @@ -119,7 +69,8 @@ name="Share" visible="true"> <on_click - function="Inventory.Share" /> + function="Inventory.GearDefault.Custom.Action" + parameter="share" /> <on_enable function="Inventory.GearDefault.Enable" parameter="share" /> @@ -154,9 +105,25 @@ function="Inventory.GearDefault.Custom.Action" parameter="replace_links" /> </menu_item_call> - <menu_item_separator - layout="topleft" /> - + <menu_item_separator> + <menu_item_separator.on_visible + function="Inventory.GearDefault.Visible" + parameter="multi_folder_view" /> + </menu_item_separator> + <menu_item_call + label="Empty Lost and Found" + layout="topleft" + name="empty_lostnfound"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="empty_lostnfound" /> + <on_enable + function="Inventory.GearDefault.Enable" + parameter="empty_lostnfound" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="multi_folder_view" /> + </menu_item_call> <menu_item_call label="Empty Trash" layout="topleft" @@ -167,5 +134,8 @@ <on_enable function="Inventory.GearDefault.Enable" parameter="empty_trash" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="multi_folder_view" /> </menu_item_call> </toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_inventory_search_visibility.xml b/indra/newview/skins/default/xui/en/menu_inventory_search_visibility.xml index 46193f4a7a..8e34f52f3a 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory_search_visibility.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory_search_visibility.xml @@ -7,6 +7,17 @@ name="menu_search_visibility" visible="false"> <menu_item_check + label="Search outfit folders" + layout="topleft" + name="search_outfits"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="toggle_search_outfits" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="toggle_search_outfits" /> + </menu_item_check> + <menu_item_check label="Search Trash" layout="topleft" name="search_trash"> diff --git a/indra/newview/skins/default/xui/en/menu_inventory_view_default.xml b/indra/newview/skins/default/xui/en/menu_inventory_view_default.xml new file mode 100644 index 0000000000..c7f9822e41 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_inventory_view_default.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<toggleable_menu + bottom="806" + layout="topleft" + left="0" + mouse_opaque="false" + name="menu_view_default" + visible="false"> + <menu_item_check + label="Sort by Name" + layout="topleft" + name="sort_by_name"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="sort_by_name" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="sort_by_name" /> + </menu_item_check> + <menu_item_check + label="Sort by Most Recent" + layout="topleft" + name="sort_by_recent"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="sort_by_recent" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="sort_by_recent" /> + </menu_item_check> + <menu_item_check + label="Sort Folders Always by Name" + layout="topleft" + name="sort_folders_by_name"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="sort_folders_by_name" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="sort_folders_by_name" /> + </menu_item_check> + <menu_item_check + label="Sort System Folders to Top" + layout="topleft" + name="sort_system_folders_to_top"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="sort_system_folders_to_top" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="sort_system_folders_to_top" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="multi_folder_view" /> + </menu_item_check> + <menu_item_separator> + <menu_item_separator.on_visible + function="Inventory.GearDefault.Visible" + parameter="single_folder_view" /> + </menu_item_separator> + <menu_item_check + label="List view" + layout="topleft" + name="list_view"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="list_view" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="list_view" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="single_folder_view" /> + </menu_item_check> + <menu_item_check + label="Gallery view" + layout="topleft" + name="gallery_view"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="gallery_view" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="gallery_view" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="single_folder_view" /> + </menu_item_check> + <menu_item_check + label="Combination view" + layout="topleft" + name="combination_view"> + <on_click + function="Inventory.GearDefault.Custom.Action" + parameter="combination_view" /> + <on_check + function="Inventory.GearDefault.Check" + parameter="combination_view" /> + <on_visible + function="Inventory.GearDefault.Visible" + parameter="single_folder_view" /> + </menu_item_check> + <menu_item_separator/> + <menu_item_check + label="Inventory settings..." + name="inv_settings"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="inventory_settings" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="inventory_settings" /> + </menu_item_check> +</toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_outfit_gear.xml b/indra/newview/skins/default/xui/en/menu_outfit_gear.xml index 32d9d28434..e216962d12 100644 --- a/indra/newview/skins/default/xui/en/menu_outfit_gear.xml +++ b/indra/newview/skins/default/xui/en/menu_outfit_gear.xml @@ -40,32 +40,11 @@ parameter="take_off" /> </menu_item_call> <menu_item_call - label="Upload Photo (L$[UPLOAD_COST])" + label="Image..." layout="topleft" - name="upload_photo"> + name="thumbnail"> <on_click - function="Gear.UploadPhoto" /> - </menu_item_call> - <menu_item_call - label="Select Photo" - layout="topleft" - name="select_photo"> - <on_click - function="Gear.SelectPhoto" /> - </menu_item_call> - <menu_item_call - label="Take a Snapshot" - layout="topleft" - name="take_snapshot"> - <on_click - function="Gear.TakeSnapshot" /> - </menu_item_call> - <menu_item_call - label="Remove Photo" - layout="topleft" - name="remove_photo"> - <on_click - function="Gear.RemovePhoto" /> + function="Gear.Thumbnail" /> </menu_item_call> <menu_item_separator name="sepatator1" /> <!-- copied (with minor modifications) from menu_inventory_add.xml --> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index a216ef7c0d..204fead7e0 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -3284,6 +3284,30 @@ See https://wiki.secondlife.com/wiki/Adding_Spelling_Dictionaries <notification icon="alertmodal.tga" + label="Rename Selected Item" + name="RenameItem" + type="alertmodal"> + Choose a new name for: +[NAME] + <tag>confirm</tag> + <form name="form"> + <input name="new_name" type="text" width="300"> + [NAME] + </input> + <button + default="true" + index="0" + name="OK" + text="OK"/> + <button + index="1" + name="Cancel" + text="Cancel"/> + </form> + </notification> + + <notification + icon="alertmodal.tga" name="RemoveFromFriends" type="alertmodal"> <tag>friendship</tag> @@ -6129,7 +6153,54 @@ Are you sure you want to delete them? notext="Cancel" yestext="OK"/> </notification> - + + <notification + icon="alertmodal.tga" + name="DeleteThumbnail" + type="alertmodal"> + <unique/> + Delete the image for this item? There is no undo. + <tag>confirm</tag> + <usetemplate + ignoretext="Don't show me this again" + name="okcancelignore" + notext="Cancel" + yestext="Delete"/> + </notification> + + <notification + icon="alertmodal.tga" + name="ThumbnailDimentionsLimit" + type="alertmodal"> + <unique/> + Only square images from 64 to 256 pixels per side are allowed. + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="ThumbnailInsufficientPermissions" + type="alertmodal"> + <unique/> + Only copy and transfer free images can be assigned as thumbnails. + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="ThumbnailOutfitPhoto" + type="alertmodal"> + <unique/> + To add an image to an outfit, use the Outfit Gallery window, or right-click on the outfit folder and select "Image..." + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + <notification icon="alertmodal.tga" name="ConfirmUnlink" @@ -6379,6 +6450,22 @@ Your inventory is experiencing issues. Please, contact support. <notification icon="alertmodal.tga" + name="InventoryLimitReachedAISAlert" + type="alertmodal"> +Your inventory is experiencing issues. Please, contact support. + <tag>fail</tag> + </notification> + + <notification + icon="notifytip.tga" + name="InventoryLimitReachedAIS" + type="notifytip"> +Your inventory is experiencing issues. Please, contact support. + <tag>fail</tag> + </notification> + + <notification + icon="alertmodal.tga" name="ConfirmClearBrowserCache" type="alertmodal"> Are you sure you want to delete your travel, web, and search history? @@ -11901,16 +11988,41 @@ Packing: [PACK_TIME]s [PSIZE]KB Unpacking: [UNPACK_TIME]s [USIZE]KB <tag>fail</tag> </notification> - + + <notification + icon="alertmodal.tga" + label="Prompt for MFA Token" + name="PromptMFAToken" + type="alertmodal"> + [MESSAGE] + <tag>confirm</tag> + <form name="form"> + <input name="token" type="text" width="400" /> + <button + default="true" + index="0" + name="continue" + text="Continue"/> + <button + index="1" + name="cancel" + text="Cancel"/> + </form> + </notification> + <notification icon="alertmodal.tga" label="Prompt for MFA Token" - name="PromptMFAToken" + name="PromptMFATokenWithSave" type="alertmodal"> [MESSAGE] <tag>confirm</tag> <form name="form"> <input name="token" type="text" width="400" /> + <ignore + name="ignore" + checkbox_only="true" + text="Remember this computer for 30 days."/> <button default="true" index="0" diff --git a/indra/newview/skins/default/xui/en/panel_inventory_gallery.xml b/indra/newview/skins/default/xui/en/panel_inventory_gallery.xml new file mode 100644 index 0000000000..ed04e12193 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_inventory_gallery.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + background_visible="true" + bg_alpha_color="InventoryBackgroundColor" + border="false" + follows="all" + height="390" + name="Inventory Gallery" + layout="topleft"> + <text + type="string" + clip_partial="false" + follows="all" + layout="topleft" + name="empty_txt" + height="390" + halign="center" + valign="center" + parse_urls="true" + wrap="true"> + Folder is empty. + </text> + <scroll_container + follows="all" + height="390" + layout="topleft" + left="0" + top="0" + tab_stop="true" + name="gallery_scroll_panel" + opaque="false"> + </scroll_container> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml b/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml new file mode 100644 index 0000000000..574872a870 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_inventory_gallery_item.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + background_visible="false" + background_opaque="false" + bg_alpha_color="FrogGreen" + bg_opaque_color="FrogGreen" + border="false" + bevel_style="none" + follows="left|top" + height="149" + width="130" + name="gallery_item_panel" + layout="topleft" + left="0" + top="0"> + <thumbnail + name="preview_thumbnail" + fallback_image="Thumbnail_Fallback" + layout="topleft" + follows="left|top" + interactable="false" + height="128" + width="128" + top="0" + left="1"/> + <icon + left="5" + top_pad="-21" + layout="topleft" + name="item_type" + height="16" + width="16" + follows="left|top" + visible="true" + image_name="Inv_Eye"/> + <icon + left="5" + top_pad="-8" + layout="topleft" + name="link_overlay" + height="8" + width="6" + follows="left|top" + visible="false" + image_name="Inv_Link"/> + <panel + background_visible="false" + background_opaque="true" + bg_opaque_color="MenuItemHighlightBgColor" + border="false" + bevel_style="none" + follows="left|top" + left="0" + top="129" + height="25" + width="130" + name="text_bg_panel"> + <text + read_only="true" + length="1" + follows="left|top" + left="1" + height="23" + layout="topleft" + name="item_name" + parse_urls="false" + text_readonly_color="White" + word_wrap="true" + top="2" + width="127" + use_ellipses="true"> + Item name, folder name. + </text> + </panel> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_main_inventory.xml b/indra/newview/skins/default/xui/en/panel_main_inventory.xml index 2ff58035ed..f7a9c552cc 100644 --- a/indra/newview/skins/default/xui/en/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_main_inventory.xml @@ -6,8 +6,7 @@ layout="topleft" min_height="300" min_width="240" - name="main inventory panel" - width="330"> + name="main inventory panel"> <panel.string name="Itemcount"> </panel.string> @@ -23,211 +22,325 @@ name="ItemcountUnknown"> Fetched [ITEM_COUNT] Items and [CATEGORY_COUNT] Folders [FILTER] </panel.string> + <panel.string name="inventory_title">INVENTORY</panel.string> + <panel.string name="default_mode_btn">Multi_Folder_Mode</panel.string> + <panel.string name="single_folder_mode_btn">Single_Folder_Mode</panel.string> <text - type="string" - length="1" - follows="left|top|right" - height="13" - layout="topleft" - left="12" - name="ItemcountText" - font="SansSerifMedium" - text_color="InventoryItemLinkColor" - use_ellipses="true" - top_pad="0" - width="300"> + type="string" + length="1" + follows="left|top|right" + height="13" + layout="topleft" + left="12" + right="-12" + name="ItemcountText" + font="SansSerifMedium" + text_color="InventoryItemLinkColor" + use_ellipses="true" + top_pad="0"> Items: </text> - <combo_box - height="23" - layout="topleft" - left="10" - top="18" - name="search_type" - follows="top|left" - width="88"> - <item - label="Name" - name="Name" - value="search_by_name"/> - <item - label="Creator" - name="Creator" - value="search_by_creator"/> - <item - label="Description" - name="Description" - value="search_by_description"/> - <item - label="UUID" - name="UUID" - value="search_by_UUID"/> - </combo_box> - <menu_button - follows="top|left" - tool_tip="Show search visibility options" - height="23" - image_overlay="Inv_Toolbar_SearchVisibility" - layout="topleft" - left_pad="3" - name="options_visibility_btn" - width="31" /> - <filter_editor - text_pad_left="10" + <layout_stack follows="left|top|right" - height="23" - label="Enter search text" - layout="topleft" - left_pad="3" - max_length_chars="300" - highlight_text_field="true" - name="inventory search editor" - width="177" /> - <tab_container - follows="all" - halign="center" - height="339" - layout="topleft" - left="7" - name="inventory filter tabs" - tab_height="30" - tab_position="top" - tab_min_width="100" - top_pad="10" - width="312"> - <inventory_panel - bg_opaque_color="DkGray2" - bg_alpha_color="DkGray2" - background_visible="true" - border="false" - bevel_style="none" - follows="all" - height="338" - label="MY INVENTORY" - help_topic="my_inventory_tab" - layout="topleft" - left="0" - name="All Items" - sort_order_setting="InventorySortOrder" - show_item_link_overlays="true" - top="16" - width="288" /> - <recent_inventory_panel - bg_opaque_color="DkGray2" - bg_alpha_color="DkGray2" - background_visible="true" + height="25" + animate="false" + top_pad="10" + left="2" + right="-4" + orientation="horizontal"> + <layout_panel border="false" - bevel_style="none" - follows="all" - height="338" - label="RECENT" - help_topic="recent_inventory_tab" - layout="topleft" - left_delta="0" - name="Recent Items" - show_item_link_overlays="true" - width="290" /> - <inventory_panel - name="Worn Items" - label="WORN" - show_empty_message="false" - follows="all" + bevel_style="in" + user_resize="false" + auto_resize="false" + height="25" + width="65" + name="nav_buttons" + visible="false"> + <button + follows="top|left" + height="23" + image_selected="Single_Folder_Back" + image_pressed="Single_Folder_Back" + image_unselected="Single_Folder_Back" + scale_image="false" + layout="topleft" + left="3" + top="2" + name="back_btn" + tool_tip="Back" + width="20" /> + <button + follows="top|left" + height="23" + image_selected="Single_Folder_Forward" + image_pressed="Single_Folder_Forward" + image_unselected="Single_Folder_Forward" + scale_image="false" + layout="topleft" + left_pad="1" + name="forward_btn" + tool_tip="Forward" + width="20" /> + <button + follows="top|left" + height="23" + image_selected="Single_Folder_Up" + image_pressed="Single_Folder_Up" + image_unselected="Single_Folder_Up" + scale_image="false" layout="topleft" - width="290" - bg_opaque_color="DkGray2" - bg_alpha_color="DkGray2" - background_visible="true" + left_pad="1" + name="up_btn" + tool_tip="Go up one level" + width="20" /> + </layout_panel> + <layout_panel border="false" - bevel_style="none" - scroll.reserve_scroll_corner="false"> - </inventory_panel> - </tab_container> - <layout_stack - animate="false" - border_size="0" - follows="left|right|bottom" - height="25" - layout="topleft" - orientation="horizontal" - top_pad="0" - left="10" - name="bottom_panel" - width="307"> - <layout_panel - auto_resize="false" - height="25" + bevel_style="in" + user_resize="false" + height="25" + width="381" + visible="true"> + <combo_box + height="23" + layout="topleft" + left="2" + top="0" + name="search_type" + tool_tip="Search by" + follows="top|left" + width="67"> + <item + label="Name" + name="Name" + value="search_by_name"/> + <item + label="Creator" + name="Creator" + value="search_by_creator"/> + <item + label="Description" + name="Description" + value="search_by_description"/> + <item + label="UUID" + name="UUID" + value="search_by_UUID"/> + </combo_box> + <menu_button + follows="top|left" + tool_tip="Search visibility options" + height="23" + image_overlay="Inv_Toolbar_SearchVisibility" layout="topleft" - name="options_gear_btn_panel" - width="32"> - <menu_button - follows="bottom|left" - tool_tip="Show additional options" - height="25" - image_hover_unselected="Toolbar_Left_Over" - image_overlay="OptionsMenu_Off" - image_selected="Toolbar_Left_Selected" - image_unselected="Toolbar_Left_Off" - layout="topleft" - left="0" - name="options_gear_btn" - top="0" - width="31" /> - </layout_panel> - <layout_panel - auto_resize="false" - height="25" + left_pad="1" + name="options_visibility_btn" + width="31" /> + <filter_editor + text_pad_left="10" + follows="left|top|right" + height="23" + label="Enter search text" layout="topleft" - name="add_btn_panel" - width="32"> - <button - follows="bottom|left" - height="25" - image_hover_unselected="Toolbar_Middle_Over" - image_overlay="AddItem_Off" - image_selected="Toolbar_Middle_Selected" - image_unselected="Toolbar_Middle_Off" - layout="topleft" - left="0" - name="add_btn" - tool_tip="Add new item" - top="0" - width="31" /> - </layout_panel> - <layout_panel - auto_resize="true" - height="25" + left_pad="1" + max_length_chars="300" + highlight_text_field="true" + name="inventory search editor" + width="150" /> + <menu_button + follows="top|right" + tool_tip="Actions" + height="23" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="OptionsMenu_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" layout="topleft" - name="dummy_panel" - width="212"> - <icon - follows="bottom|left|right" - height="25" - image_name="Toolbar_Middle_Off" - layout="topleft" - left="0" - top="0" - name="dummy_icon" - width="211" /> - </layout_panel> - <layout_panel - auto_resize="false" - height="25" + left_pad="1" + name="options_gear_btn" + width="31" /> + <menu_button + follows="top|right" + tool_tip="View & sort options" + height="23" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Conv_toolbar_sort" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="1" + name="view_btn" + width="31" /> + <button + follows="top|right" + height="23" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="AddItem_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="1" + name="add_btn" + tool_tip="Create new item" + width="31" /> + <button + follows="top|right" + tool_tip="Switch between views" + height="23" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Single_Folder_Mode" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + left_pad="1" + name="view_mode_btn" + width="31" /> + </layout_panel> + </layout_stack> + <panel + follows="all" + halign="center" + height="372" + layout="topleft" + left="3" + right="-3" + name="default_inventory_panel" + top_pad="5"> + <tab_container + follows="all" + halign="center" + height="372" layout="topleft" - name="trash_btn_panel" - width="31"> - <dnd_button - follows="bottom|left" - height="25" - image_hover_unselected="Toolbar_Right_Over" - image_overlay="TrashItem_Off" - image_selected="Toolbar_Right_Selected" - image_unselected="Toolbar_Right_Off" - left="0" - layout="topleft" - name="trash_btn" - tool_tip="Remove selected item" - top="0" - width="31"/> - </layout_panel> - </layout_stack> + left="0" + name="inventory filter tabs" + tab_height="30" + tab_position="top" + tab_min_width="100" + top="0"> + <inventory_panel + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + follows="all" + label="MY INVENTORY" + help_topic="my_inventory_tab" + layout="topleft" + name="All Items" + sort_order_setting="InventorySortOrder" + show_item_link_overlays="true" + preinitialize_views="false" + scroll.reserve_scroll_corner="false"> + <folder double_click_override="true"/> + </inventory_panel> + <recent_inventory_panel + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + follows="all" + label="RECENT" + help_topic="recent_inventory_tab" + layout="topleft" + name="Recent Items" + show_item_link_overlays="true" + preinitialize_views="false" + scroll.reserve_scroll_corner="false"> + <folder double_click_override="true"/> + </recent_inventory_panel> + <inventory_panel + name="Worn Items" + label="WORN" + show_empty_message="false" + follows="all" + layout="topleft" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + preinitialize_views="false" + border="false" + bevel_style="none" + scroll.reserve_scroll_corner="false"> + <folder double_click_override="true"/> + </inventory_panel> + </tab_container> + </panel> + <panel + follows="all" + halign="center" + height="375" + layout="topleft" + left="7" + name="combination_view_inventory" + top_delta="0" + visible="false"> + <layout_stack + follows="all" + layout="topleft" + height="375" + name="combination_view_stack" + animate="false" + drag_handle_thickness="6" + drag_handle_first_indent="18" + drag_handle_second_indent="18" + drag_handle_shift="5" + show_drag_handle="true" + top="0" + left="0" + orientation="vertical"> + <layout_panel + border="false" + bevel_style="in" + user_resize="true" + auto_resize="true" + height="248" + min_width="150" + name="comb_gallery_layout"> + <panel + class="inventory_gallery" + filename="panel_inventory_gallery.xml" + left="0" + top="1" + height="248" + name="comb_gallery_view_inv" + background_visible="true" + follows="all" + layout="topleft"> + </panel> + </layout_panel> + <layout_panel + border="false" + bevel_style="in" + user_resize="true" + auto_resize="true" + height="127" + min_height="100" + name="comb_inventory_layout"> + <single_folder_inventory_panel + name="comb_single_folder_inv" + follows="all" + left="0" + top="1" + height="127" + layout="topleft" + show_item_link_overlays="true" + bg_opaque_color="DkGray2" + bg_alpha_color="DkGray2" + background_visible="true" + border="false" + bevel_style="none" + scroll.reserve_scroll_corner="false"> + <item + single_folder_mode="true" + folder_indentation="-8"/> + <folder + single_folder_mode="true" + folder_indentation="-8"/> + </single_folder_inventory_panel> + </layout_panel> + </layout_stack> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml index e3790ae09b..e951d25391 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml @@ -40,50 +40,10 @@ layout="topleft" left="4" top="0" + tab_stop="true" name="gallery_scroll_panel" opaque="false" top_pad="0"> - <!--outfit_gallery_item - layout="topleft" - left="10" - name="preview_outfit1" - height="175" - width="150" - follows="left|top"/--> - <!--layout_stack follows="left|right" height="180" width="498" layout="topleft" left="0" animate="false" top="0" name="top_gallery_stack" orientation="horizontal"> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top_gallery_panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit1" height="175" width="150" follows="left|top"/> - </layout_panel> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/> - </layout_panel> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/> - </layout_panel> - </layout_stack> - <layout_stack follows="left|right" height="180" width="498" layout="topleft" left="0" animate="false" top="190" name="top_gallery_stack" orientation="horizontal"> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top_gallery_panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit1" height="175" width="150" follows="left|top"/> - </layout_panel> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/> - </layout_panel> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/> - </layout_panel> - </layout_stack> - <layout_stack follows="left|right" height="180" width="498" layout="topleft" left="0" animate="false" top="380" name="top_gallery_stack" orientation="horizontal"> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top_gallery_panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit1" height="175" width="150" follows="left|top"/> - </layout_panel> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/> - </layout_panel> - <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel"> - <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/> - </layout_panel> - </layout_stack--> - <!--</panel>--> </scroll_container> <panel background_visible="true" diff --git a/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml b/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml index b2dc975c6e..ceaff0ea69 100644 --- a/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml +++ b/indra/newview/skins/default/xui/en/panel_outfits_wearing.xml @@ -18,7 +18,7 @@ follows="all" height="400" layout="topleft" - left="0" + left="3" single_expansion="true" top="0" name="wearables_accordion" diff --git a/indra/newview/skins/default/xui/en/panel_profile_firstlife.xml b/indra/newview/skins/default/xui/en/panel_profile_firstlife.xml index ca1e405a62..f899f83ad4 100644 --- a/indra/newview/skins/default/xui/en/panel_profile_firstlife.xml +++ b/indra/newview/skins/default/xui/en/panel_profile_firstlife.xml @@ -19,7 +19,7 @@ layout="topleft" visible="false" /> - <icon + <thumbnail name="real_world_pic" image_name="Generic_Person_Large" follows="top|left" diff --git a/indra/newview/skins/default/xui/en/panel_profile_secondlife.xml b/indra/newview/skins/default/xui/en/panel_profile_secondlife.xml index 07cdd6d71e..fea7d1bcb8 100644 --- a/indra/newview/skins/default/xui/en/panel_profile_secondlife.xml +++ b/indra/newview/skins/default/xui/en/panel_profile_secondlife.xml @@ -68,7 +68,7 @@ Account: [ACCTTYPE] auto_resize="false" user_resize="false"> - <icon + <thumbnail name="2nd_life_pic" image_name="Generic_Person_Large" layout="topleft" diff --git a/indra/newview/skins/default/xui/en/sidepanel_inventory.xml b/indra/newview/skins/default/xui/en/sidepanel_inventory.xml index 9995523e61..76d0ffcb8e 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_inventory.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_inventory.xml @@ -6,8 +6,7 @@ layout="topleft" min_height="350" min_width="240" - name="objects panel" - width="333"> + name="objects panel"> <panel follows="all" layout="topleft" @@ -17,25 +16,22 @@ label="" height="570" visible="true" - default_tab_group="1" - width="330"> + default_tab_group="1"> <layout_stack follows="left|right|top|bottom" layout="topleft" left="0" top="0" - tab_group="1" + tab_group="1" orientation="vertical" name="inventory_layout_stack" - height="535" - width="330"> + height="560"> <layout_panel name="main_inventory_layout_panel" layout="topleft" auto_resize="true" user_resize="true" min_dim="150" - width="330" follows="bottom|left|right" height="300"> <panel @@ -47,17 +43,15 @@ name="panel_main_inventory" top="0" label="" - height="300" - width="330" /> + height="300" /> </layout_panel> <layout_panel - width="330" layout="topleft" auto_resize="false" user_resize="true" follows="left|right|top" name="inbox_layout_panel" - visible="false" + visible="true" min_dim="35" expanded_min_dim="90" height="235"> @@ -69,17 +63,15 @@ class="panel_marketplace_inbox" top="0" label="" - height="235" - width="330"> + height="235"> <string name="InboxLabelWithArg">Received items ([NUM])</string> <string name="InboxLabelNoArg">Received items</string> <button control_name="InventoryInboxToggleState" label="Received items" - font="SansSerifMedium" + font="SansSerifMedium" name="inbox_btn" height="35" - width="308" image_unselected="MarketplaceBtn_Off" image_selected="MarketplaceBtn_Selected" halign="left" @@ -89,7 +81,8 @@ tab_stop="false" pad_left="35" top="0" - left="10" /> + left="5" + right="-5" /> <text type="string" length="1" @@ -101,174 +94,35 @@ name="inbox_fresh_new_count" font="SansSerifMedium" halign="right" - top_pad="0" - width="300"> + top_pad="0"> [NUM] new </text> <panel name="inbox_inventory_placeholder_panel" follows="all" - left="10" - bottom="235" - width="308" + left="5" + right="-5" top="35" + height="200" bg_opaque_color="InventoryBackgroundColor" background_visible="true" background_opaque="true" tool_tip="Drag and drop items to your inventory to use them" > <text name="inbox_inventory_placeholder" - type="string" - follows="all" - layout="topleft" - top="0" - left="0" - width="308" - height="200" - wrap="true" - halign="center"> -Purchases from the marketplace will be delivered here. - </text> + type="string" + follows="all" + layout="topleft" + top="0" + height="200" + wrap="true" + halign="center" + valign="center"> + Purchases from the marketplace will be delivered here. + </text> </panel> </panel> </layout_panel> </layout_stack> - <panel follows="bottom|left|right" - height="30" - layout="topleft" - name="button_panel" - left="9" - top_pad="7" - width="308"> - <layout_stack follows="bottom|left|right" - height="23" - layout="topleft" - mouse_opaque="false" - name="button_panel_ls" - left="0" - orientation="horizontal" - top="0" - width="308"> - <layout_panel follows="bottom|left|right" - height="23" - layout="bottomleft" - left="0" - mouse_opaque="false" - name="info_btn_lp" - auto_resize="true" - width="101"> - <button enabled="true" - follows="bottom|left|right" - height="23" - label="Profile" - layout="topleft" - left="1" - name="info_btn" - tool_tip="Show object profile" - top="0" - width="100" /> - </layout_panel> - <layout_panel - follows="bottom|left|right" - height="23" - layout="bottomleft" - left_pad="1" - mouse_opaque="false" - name="share_btn_lp" - auto_resize="true" - width="100"> - <button - enabled="true" - follows="bottom|left|right" - height="23" - label="Share" - layout="topleft" - left="1" - name="share_btn" - tool_tip="Share an inventory item" - top="0" - width="99" /> - </layout_panel> - <layout_panel - follows="bottom|left|right" - height="23" - layout="bottomleft" - left_pad="1" - mouse_opaque="false" - name="shop_btn_lp" - auto_resize="true" - width="100"> - <button - enabled="true" - follows="bottom|left|right" - height="23" - label="Shop" - layout="topleft" - left="1" - name="shop_btn" - tool_tip="Open Marketplace webpage" - top="0" - width="99" /> - <button - enabled="false" - follows="bottom|left|right" - height="23" - label="Wear" - layout="topleft" - left="1" - name="wear_btn" - tool_tip="Wear seleceted outfit" - top="0" - width="99" /> - <button - enabled="false" - follows="bottom|left|right" - height="23" - label="Play" - layout="topleft" - name="play_btn" - left="1" - top="0" - width="99" /> - <button - enabled="false" - follows="bottom|left|right" - height="23" - label="Teleport" - layout="topleft" - left="1" - name="teleport_btn" - tool_tip="Teleport to the selected area" - top="0" - width="99" /> - </layout_panel> - </layout_stack> - </panel> - </panel> - <panel - follows="all" - layout="topleft" - left="0" - class="sidepanel_item_info" - filename="sidepanel_item_info.xml" - name="sidepanel__item_panel" - top="0" - label="" - height="570" - visible="false" - width="330"> - </panel> - <panel - follows="all" - layout="topleft" - left="0" - class="sidepanel_task_info" - filename="sidepanel_task_info.xml" - name="sidepanel__task_panel" - top="0" - label="" - height="570" - visible="false" - width="330"> </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml index 35d14251c7..ad521cb1af 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml @@ -43,214 +43,126 @@ name="origin_inworld"> (Inworld) </panel.string> - <icon - follows="top|right" - height="18" - image_name="Lock" - layout="topleft" - right="-15" - mouse_opaque="true" - name="IconLocked" - top="8" - width="18" /> - <button - follows="top|left" - height="24" - image_hover_unselected="BackButton_Over" - image_pressed="BackButton_Press" - image_unselected="BackButton_Off" - layout="topleft" - left="12" - name="back_btn" - tab_stop="false" - top="2" - width="30" - use_draw_context_alpha="false" /> - <text - follows="top|left|right" - font="SansSerifHugeBold" - height="26" - layout="topleft" - left_pad="3" - name="title" - text_color="LtGray" - top="2" - use_ellipses="true" - value="Item Profile" - width="275" /> - <text - follows="top|left|right" - height="13" - layout="topleft" - left="45" - name="origin" - text_color="LtGray_50" - use_ellipses="true" - value="(Inventory)" - width="275" /> - <scroll_container - color="DkGray2" - follows="all" - layout="topleft" - left="9" - name="item_profile_scroll" - opaque="true" - height="493" - width="313" - top="45"> - <panel - follows="left|top|right" - height="390" - help_topic="" - label="" + +<layout_stack + animate="false" + name="main_stack" + layout="topleft" + follows="all" + orientation="vertical" + left="0" + top="0" + right="-1" + bottom="-1"> + <layout_panel + auto_resize="false" + name="layout_item_name" layout="topleft" - left="0" - name="item_profile" - top="0" - width="295"> - <text - type="string" - length="1" - follows="left|top" - height="10" + follows="all" + height="25"> + <icon + follows="top|left" + height="16" + image_name="Inv_Object" layout="topleft" left="5" - name="LabelItemNameTitle" - top="10" - width="78"> - Name: - </text> + mouse_opaque="true" + name="item_type_icon" + top="3" + width="16" /> <line_editor border_style="line" border_thickness="1" follows="left|top|right" - height="20" layout="topleft" - left_delta="78" + left_pad="5" + top="1" + right="-5" + height="20" max_length_bytes="63" name="LabelItemName" - top_delta="0" - width="210" tool_tip="The name is limited to 63 characters. Longer prim names are cut short. Names can only consist of printable characters found in the ASCII-7 (non-extended) character set, with the exception of the vertical bar/pipe '|'." /> - <text - type="string" - length="1" - follows="left|top" - height="10" + </layout_panel> + + <layout_panel + auto_resize="false" + name="layout_item_details" + layout="topleft" + follows="all" + height="133"> + + <thumbnail + name="item_thumbnail" + fallback_image="Thumbnail_Fallback" + follows="top|left" layout="topleft" left="5" - name="LabelItemDescTitle" - top_pad="10" - width="78"> - Description: - </text> - <line_editor - border_style="line" - border_thickness="1" - follows="left|top|right" - height="23" - layout="topleft" - left_delta="78" - max_length_bytes="127" - name="LabelItemDesc" - top_delta="-5" - width="210" - tool_tip="When people have 'Hover Tips on All Objects' selected in the viewer's settings, they'll see the object description pop-up for any object under their mouse pointer. The prim description is limited to 127 bytes any string longer then that will be truncated." /> + top="2" + height="128" + width="128" + /> + <text type="string" length="1" follows="left|top" - height="23" + height="16" layout="topleft" - left="5" - name="LabelCreatorTitle" - top_pad="10" + left_pad="5" + name="LabelOwnerTitle" + top="0" width="78"> - Creator: + Owner: </text> - <avatar_icon - follows="top|left" - height="20" - default_icon_name="Generic_Person" - layout="topleft" - left_pad="0" - top_delta="-6" - mouse_opaque="true" - width="20" /> <text type="string" - follows="left|right|top" font="SansSerifSmall" - height="15" + follows="left|right|top" layout="topleft" - left_pad="5" - name="LabelCreatorName" - top_delta="6" + height="15" + width="187" + left_delta="0" + top_pad="0" + name="LabelOwnerName" use_ellipses="true" - width="165"> + translate="false"> +TestString PleaseIgnore </text> - <button - follows="top|right" - height="16" - image_selected="Inspector_I" - image_unselected="Inspector_I" - layout="topleft" - right="-5" - name="BtnCreator" - top_delta="-6" - width="16" /> <text type="string" length="1" follows="left|top" - height="23" + height="16" layout="topleft" - left="5" - name="LabelOwnerTitle" - top_pad="10" + left_delta="0" + name="LabelCreatorTitle" + top_pad="7" width="78"> - Owner: + Creator: </text> - <avatar_icon - follows="top|left" - height="20" - default_icon_name="Generic_Person" - layout="topleft" - left_pad="0" - top_delta="-6" - mouse_opaque="true" - width="20" /> <text type="string" - follows="left|right|top" font="SansSerifSmall" - height="15" + follows="left|right|top" layout="topleft" - left_pad="5" - name="LabelOwnerName" - top_delta="6" + left_delta="0" + top_pad="0" + width="187" + height="15" + name="LabelCreatorName" use_ellipses="true" - width="165"> + translate="false"> +TestString PleaseIgnore </text> - <button - follows="top|right" - height="16" - image_selected="Inspector_I" - image_unselected="Inspector_I" - layout="topleft" - right="-5" - name="BtnOwner" - top_delta="-3" - width="16" /> <text type="string" length="1" follows="left|top" - height="23" + height="16" layout="topleft" - left="5" + left_delta="0" name="LabelAcquiredTitle" - top_pad="10" + top_pad="7" width="78"> Acquired: </text> @@ -258,171 +170,247 @@ type="string" length="1" follows="left|top|right" - height="23" + height="18" layout="topleft" - left_delta="78" + left_delta="0" name="LabelAcquiredDate" - top_delta="0" - width="210"> + top_pad="0" + width="187"> + 00/00/00 </text> - <text - type="string" - length="1" + <button follows="left|top" - height="10" + height="21" + label="Image..." layout="topleft" - left="5" - name="LabelItemExperienceTitle" + left_delta="0" + name="change_thumbnail_btn" top_pad="0" - width="78" - visible="true"> - Experience: - </text> + width="120" /> + </layout_panel> + + <layout_panel + auto_resize="false" + name="layout_item_description" + layout="topleft" + follows="all" + height="84"> <text type="string" length="1" - follows="left|top|right" + follows="left|top" height="10" layout="topleft" - left_delta="78" - name="LabelItemExperience" - top_delta="0" - width="210" - visible="true" - /> - <panel - border="false" - follows="left|top|right" + left="5" + name="LabelItemDescTitle" + top="0" + width="78"> + Description: + </text> + <text_editor + text_type="ascii_printable_no_pipe" + commit_on_focus_lost="true" + border_style="line" + border_thickness="1" + word_wrap="true" + use_ellipses="false" + follows="all" layout="topleft" - mouse_opaque="false" - name="perms_inv" - left="0" - top_pad="25" - height="155" - width="313"> - <text - type="string" - length="1" - left="10" - top_pad="13" - text_color="EmphasisColor" - height="15" - follows="left|top|right" - layout="topleft" - name="perm_modify" - width="200"> - You can: - </text> - <check_box - height="18" - label="Modify" - layout="topleft" - left="20" - name="CheckOwnerModify" - top_pad="0" - width="90" /> - <check_box - height="18" - label="Copy" - layout="topleft" - left_pad="0" - name="CheckOwnerCopy" - width="90" /> - <check_box - height="18" - label="Transfer" - layout="topleft" - left_pad="0" - name="CheckOwnerTransfer" - width="106" /> - <text - type="string" - length="1" - follows="left|top" - height="16" - layout="topleft" - left="10" - name="AnyoneLabel" - top_pad="8" - width="100"> - Anyone: - </text> - <check_box - height="18" - label="Copy" - layout="topleft" - left_pad="0" - name="CheckEveryoneCopy" - tool_tip="Anyone can take a copy of the object . Object and all of its contents must be copy and transfer permissive." - top_delta="-2" - width="150" /> + left="5" + top_pad="5" + right="-5" + height="46" + max_length="127" + name="LabelItemDesc" + tool_tip="When people have 'Hover Tips on All Objects' selected in the viewer's settings, they'll see the object description pop-up for any object under their mouse pointer. The prim description is limited to 127 bytes any string longer then that will be truncated." /> + <text type="string" length="1" follows="left|top" - height="16" + height="10" layout="topleft" - left="10" - name="GroupLabel" - top_pad="8" - width="100"> - Group: + left="5" + name="LabelItemExperienceTitle" + top_pad="7" + width="78" + visible="true"> + Experience: </text> - <check_box - height="18" - label="Share" - layout="topleft" - left_pad="0" - top_delta="-2" - name="CheckShareWithGroup" - tool_tip="Allow all members of the set group to share your modify permissions for this object. You must Deed to enable role restrictions." - width="150" /> <text type="string" length="1" - follows="left|top" - height="16" - layout="topleft" - left="10" - name="NextOwnerLabel" - top_pad="8" - width="200" - word_wrap="true"> - Next owner: - </text> - <check_box - height="18" - label="Modify" - layout="topleft" - left="20" - top_pad="0" - name="CheckNextOwnerModify" - tool_tip="Next owner can edit properties like item name or scale of this object." - width="90" /> - <check_box - height="18" - label="Copy" - layout="topleft" - left_pad="0" - name="CheckNextOwnerCopy" - tool_tip="Next owner can make unlimited copies of this object. Copies maintain creator information, and can never be more permissive than the item being copied." - width="90" /> - <check_box - height="18" - label="Transfer" + follows="left|top|right" + height="10" layout="topleft" - left_pad="0" - name="CheckNextOwnerTransfer" - tool_tip="Next owner can give away or resell this object." - width="106" /> - </panel> + left_delta="78" + name="LabelItemExperience" + top_delta="0" + width="210" + visible="true" + /> + </layout_panel> + + <layout_panel + auto_resize="false" + name="layout_item_permissions_sale" + layout="topleft" + follows="all" + height="235"> + + <view_border + bevel_style="none" + height="0" + layout="topleft" + left="5" + right="-5" + name="cost_text_border" + top="1"/> + + <text + type="string" + length="1" + left="10" + top_pad="7" + height="15" + follows="left|top" + layout="topleft" + name="perm_modify" + width="200"> + Permissions + </text> + + <text + type="string" + length="1" + left="10" + top_pad="5" + height="15" + follows="left|top" + layout="topleft" + name="perm_modify" + width="200"> + You can: + </text> + <check_box + height="18" + label="Modify" + layout="topleft" + left="20" + name="CheckOwnerModify" + top_pad="0" + width="90" /> + <check_box + height="18" + label="Copy" + layout="topleft" + left_pad="0" + name="CheckOwnerCopy" + width="90" /> + <check_box + height="18" + label="Transfer" + layout="topleft" + left_pad="0" + name="CheckOwnerTransfer" + width="106" /> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="AnyoneLabel" + top_pad="8" + width="100"> + Anyone: + </text> + <check_box + height="18" + label="Copy" + layout="topleft" + left_pad="0" + name="CheckEveryoneCopy" + tool_tip="Anyone can take a copy of the object . Object and all of its contents must be copy and transfer permissive." + top_delta="-2" + width="150" /> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="GroupLabel" + top_pad="8" + width="100"> + Group: + </text> + <check_box + height="18" + label="Share" + layout="topleft" + left_pad="0" + top_delta="-2" + name="CheckShareWithGroup" + tool_tip="Allow all members of the set group to share your modify permissions for this object. You must Deed to enable role restrictions." + width="150" /> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="NextOwnerLabel" + top_pad="8" + width="200" + word_wrap="true"> + Next owner: + </text> + <check_box + height="18" + label="Modify" + layout="topleft" + left="20" + top_pad="0" + name="CheckNextOwnerModify" + tool_tip="Next owner can edit properties like item name or scale of this object." + width="90" /> + <check_box + height="18" + label="Copy" + layout="topleft" + left_pad="0" + name="CheckNextOwnerCopy" + tool_tip="Next owner can make unlimited copies of this object. Copies maintain creator information, and can never be more permissive than the item being copied." + width="90" /> + <check_box + height="18" + label="Transfer" + layout="topleft" + left_pad="0" + name="CheckNextOwnerTransfer" + tool_tip="Next owner can give away or resell this object." + width="106" /> + + <view_border + bevel_style="none" + height="0" + layout="topleft" + left="5" + right="-5" + name="cost_text_border" + top_pad="9"/> + <check_box height="18" label="For Sale" layout="topleft" left="20" name="CheckPurchase" - top_pad="20" + top_pad="15" width="100" tool_tip="Lets people buy this object, its content or it copy inworld for specified price." /> <combo_box @@ -450,6 +438,7 @@ follows="left|top" decimal_digits="0" increment="1" + control_name="Edit Cost" name="Edit Cost" label="Price: L$" label_width="75" @@ -460,88 +449,80 @@ max_val="999999999" top_pad="10" tool_tip="Object cost." /> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left="10" - name="BaseMaskDebug" - text_color="White" - top_pad="30" - width="130"> - B: - </text> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left_delta="60" - name="OwnerMaskDebug" - text_color="White" - top_delta="0" - width="270"> - O: - </text> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left_delta="60" - name="GroupMaskDebug" - text_color="White" - top_delta="0" - width="210"> - G: - </text> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left_delta="60" - name="EveryoneMaskDebug" - text_color="White" - top_delta="0" - width="150"> - E: - </text> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left_delta="60" - name="NextMaskDebug" - text_color="White" - top_delta="0" - width="90"> - N: - </text> - </panel> - </scroll_container> - <panel - height="30" - layout="topleft" - name="button_panel" - left="5" - top_pad="0" - width="313" - follows="top|right|left"> - <button - follows="top|right" - height="23" - label="Cancel" - layout="topleft" - name="cancel_btn" - right="-1" - width="100" /> - </panel> + + </layout_panel> + + <layout_panel + auto_resize="false" + name="layout_debug_permissions" + layout="topleft" + follows="all" + height="30"> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left="10" + name="BaseMaskDebug" + text_color="White" + top="2" + width="130"> + B: + </text> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left_delta="60" + name="OwnerMaskDebug" + text_color="White" + top_delta="0" + width="270"> + O: + </text> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left_delta="60" + name="GroupMaskDebug" + text_color="White" + top_delta="0" + width="210"> + G: + </text> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left_delta="60" + name="EveryoneMaskDebug" + text_color="White" + top_delta="0" + width="150"> + E: + </text> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left_delta="60" + name="NextMaskDebug" + text_color="White" + top_delta="0" + width="90"> + N: + </text> + </layout_panel> + </layout_stack> </panel> diff --git a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml index 5f0241512a..faff6185ab 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml @@ -62,25 +62,12 @@ name="Sale Mixed"> Mixed Sale </panel.string> - <button - follows="top|left" - height="24" - image_hover_unselected="BackButton_Over" - image_pressed="BackButton_Press" - image_unselected="BackButton_Off" - layout="topleft" - left="8" - name="back_btn" - tab_stop="false" - top="0" - width="30" - use_draw_context_alpha="false" /> - <text + <text follows="top|left|right" font="SansSerifHuge" height="26" layout="topleft" - left_pad="10" + left="48" name="title" text_color="LtGray" top="0" @@ -181,7 +168,6 @@ translate="false" use_ellipses="true" width="225"> - TestString PleaseIgnore </text> <text type="string" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 09ccc7b8d2..d0e09492b3 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2306,6 +2306,8 @@ For AI Character: Get the closest navigable point to the point provided. <string name="FavoritesNoMatchingItems">To add a place to your favorites, click the star to the right of the location name, then save the landmark to "Favorites bar".</string> <string name="MarketplaceNoListing">You have no listings yet.</string> <string name="MarketplaceNoMatchingItems">No items found. Check the spelling of your search string and try again.</string> + <string name="InventorySingleFolderEmpty">Folder is empty.</string> + <string name="InventorySingleFolderNoMatches">No matches.</string> <string name="InventoryNoTexture">You do not have a copy of this texture in your inventory</string> <string name="InventoryInboxNoItems">Your Marketplace purchases will appear here. You may then drag them into your inventory to use them.</string> <string name="MarketplaceURL">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/</string> @@ -2353,6 +2355,7 @@ If you continue to receive this message, please contact Second Life support for <string name="InventoryMarketplaceListingsNoItems"> Drag folders to this area to list them for sale on the [[MARKETPLACE_DASHBOARD_URL] Marketplace]. </string> + <string name="InventoryFolderDebug">( F:[FOLDER_COUNT] I:[ITEMS_COUNT] V:[VERSION] DC:[VIEWER_DESCENDANT_COUNT]/[SERVER_DESCENDANT_COUNT] )</string> <string name="InventoryItemsCount">( [ITEMS_COUNT] Items )</string> <string name="Marketplace Validation Log"></string> <string name="Marketplace Validation Warning Stock">stock folder must be contained by a version folder</string> @@ -2391,12 +2394,16 @@ If you continue to receive this message, please contact Second Life support for <string name="Unconstrained">Unconstrained</string> <!-- use value="" because they have preceding spaces --> + <string name="active" value=" (active)"/> <string name="no_transfer" value=" (no transfer)" /> <string name="no_modify" value=" (no modify)" /> <string name="no_copy" value=" (no copy)" /> <string name="worn" value=" (worn)" /> - <string name="link" value=" (link)" /> - <string name="broken_link" value=" (broken_link)" /> + <string name="link" value=" link" /> + <string name="broken_link" value=" broken_link" /> + <string name="no_transfer_lbl" value=" no transfer" /> + <string name="no_modify_lbl" value=" no modify" /> + <string name="no_copy_lbl" value=" no copy" /> <string name="LoadingContents">Loading contents...</string> <string name="NoContents">No contents</string> <string name="WornOnAttachmentPoint" value=" (worn on [ATTACHMENT_POINT])" /> diff --git a/indra/newview/skins/default/xui/es/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/es/floater_inventory_item_properties.xml deleted file mode 100644 index a8a3ad08f8..0000000000 --- a/indra/newview/skins/default/xui/es/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="PROPIEDADES DEL ÍTEM DEL INVENTARIO"> - <floater.string name="unknown">(desconocido)</floater.string> - <floater.string name="public">(público)</floater.string> - <floater.string name="you_can">Usted puede:</floater.string> - <floater.string name="owner_can">El propietario puede:</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local][day,datetime,local] [mth,datetime,local] [year,datetime,local][hour,datetime,local]:[min,datetime,local]:[second,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Nombre:</text> - <text name="LabelItemDescTitle">Descripción:</text> - <text name="LabelCreatorTitle">Creador:</text> - <button label="Perfil..." label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle">Propietario:</text> - <button label="Perfil..." label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle">Adquirido:</text> - <text name="LabelAcquiredDate">May Mié 24 12:50:46 2006</text> - <text name="OwnerLabel">Tú:</text> - <check_box label="Editar" name="CheckOwnerModify"/> - <check_box label="Copiarlo" left_delta="88" name="CheckOwnerCopy"/> - <check_box label="Revender" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">Cualquiera:</text> - <check_box label="Copiar" name="CheckEveryoneCopy"/> - <text name="GroupLabel">Grupo:</text> - <check_box label="Compartir" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel" width="230">Próximo propietario:</text> - <check_box label="Editar" name="CheckNextOwnerModify"/> - <check_box label="Copiarlo" left_delta="88" name="CheckNextOwnerCopy"/> - <check_box label="Revender" name="CheckNextOwnerTransfer"/> - <check_box label="En venta" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Copiar" name="Copy"/> - <combo_box.item label="Contenidos" name="Contents"/> - <combo_box.item label="Original" name="Original"/> - </combo_box> - <spinner label="Precio:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/es/panel_main_inventory.xml b/indra/newview/skins/default/xui/es/panel_main_inventory.xml index 1252c7ce0d..bf1205046b 100644 --- a/indra/newview/skins/default/xui/es/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/es/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> [ITEM_COUNT] Objetos y [CATEGORY_COUNT] Carpetas Obtenidos [FILTER] </panel.string> + <panel.string name="inventory_title">INVENTARIO</panel.string> <text name="ItemcountText"> Ítems: </text> diff --git a/indra/newview/skins/default/xui/fr/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/fr/floater_inventory_item_properties.xml deleted file mode 100644 index 1d4e7c818f..0000000000 --- a/indra/newview/skins/default/xui/fr/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="PROPRIÉTÉS DES ARTICLES DE L'INVENTAIRE"> - <floater.string name="unknown">(inconnu)</floater.string> - <floater.string name="public">(public)</floater.string> - <floater.string name="you_can">Vous pouvez :</floater.string> - <floater.string name="owner_can">Le propriétaire peut :</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Nom :</text> - <text name="LabelItemDescTitle">Description :</text> - <text name="LabelCreatorTitle">Créateur :</text> - <button label="Profil..." label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle">Propriétaire :</text> - <button label="Profil..." label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle">Acquis :</text> - <text name="LabelAcquiredDate">Wed May 24 12:50:46 2006</text> - <text name="OwnerLabel">Vous :</text> - <check_box label="Modifier" name="CheckOwnerModify"/> - <check_box label="Copier" name="CheckOwnerCopy"/> - <check_box label="Revendre" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel" width="80">N'importe qui :</text> - <check_box label="Copier" name="CheckEveryoneCopy"/> - <text name="GroupLabel" width="80">Groupe :</text> - <check_box label="Partager" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel" width="192">Le prochain propriétaire :</text> - <check_box label="Modifier" name="CheckNextOwnerModify"/> - <check_box label="Copier" name="CheckNextOwnerCopy"/> - <check_box label="Revendre" name="CheckNextOwnerTransfer"/> - <check_box label="À vendre" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Copier" name="Copy"/> - <combo_box.item label="Contenu" name="Contents"/> - <combo_box.item label="Original" name="Original"/> - </combo_box> - <spinner label="Prix :" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/fr/panel_main_inventory.xml b/indra/newview/skins/default/xui/fr/panel_main_inventory.xml index 5bcee89752..5bf4d6c15d 100644 --- a/indra/newview/skins/default/xui/fr/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/fr/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> Recherche effectuée [ITEM_COUNT] d'articles et [CATEGORY_COUNT] de dossiers [FILTER] </panel.string> + <panel.string name="inventory_title">INVENTAIRE</panel.string> <text name="ItemcountText"> Articles : </text> diff --git a/indra/newview/skins/default/xui/it/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/it/floater_inventory_item_properties.xml deleted file mode 100644 index 8cf680b3f0..0000000000 --- a/indra/newview/skins/default/xui/it/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="CARATTERISTICHE DELL'ARTICOLO IN INVENTARIO"> - <floater.string name="unknown">(sconosciuto)</floater.string> - <floater.string name="public">(pubblico)</floater.string> - <floater.string name="you_can">Tu puoi:</floater.string> - <floater.string name="owner_can">Il proprietario può:</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Nome:</text> - <text name="LabelItemDescTitle">Descrizione:</text> - <text name="LabelCreatorTitle">Creatore:</text> - <button label="Profilo..." label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle">proprietario:</text> - <button label="Profilo..." label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle">Acquisito:</text> - <text name="LabelAcquiredDate">Wed May 24 12:50:46 2006</text> - <text name="OwnerLabel">Tu:</text> - <check_box label="Modifica" name="CheckOwnerModify"/> - <check_box label="Copiare" left_delta="88" name="CheckOwnerCopy"/> - <check_box label="Rivendi" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">Chiunque:</text> - <check_box label="Copia" name="CheckEveryoneCopy"/> - <text name="GroupLabel">Gruppo:</text> - <check_box label="Condividi" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel" width="230">Proprietario successivo:</text> - <check_box label="Modifica" name="CheckNextOwnerModify"/> - <check_box label="Copiare" left_delta="88" name="CheckNextOwnerCopy"/> - <check_box label="Rivendi" name="CheckNextOwnerTransfer"/> - <check_box label="In vendita" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Copia" name="Copy"/> - <combo_box.item label="Contenuti" name="Contents"/> - <combo_box.item label="Originale" name="Original"/> - </combo_box> - <spinner label="Prezzo:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/it/panel_main_inventory.xml b/indra/newview/skins/default/xui/it/panel_main_inventory.xml index 5d11967cee..d6890229e7 100644 --- a/indra/newview/skins/default/xui/it/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/it/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> Recuperati [ITEM_COUNT] oggetti e [CATEGORY_COUNT] cartelle [FILTER] </panel.string> + <panel.string name="inventory_title">INVENTARIO</panel.string> <text name="ItemcountText"> Oggetti: </text> diff --git a/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml deleted file mode 100644 index 2a8c9f83b2..0000000000 --- a/indra/newview/skins/default/xui/ja/floater_inventory_item_properties.xml +++ /dev/null @@ -1,68 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="インベントリアイテムのプロパティ"> - <floater.string name="unknown"> - (不明) - </floater.string> - <floater.string name="public"> - (公共) - </floater.string> - <floater.string name="you_can"> - できること: - </floater.string> - <floater.string name="owner_can"> - オーナーは次のことができます: - </floater.string> - <floater.string name="acquiredDate"> - [year,datetime,local]年[mth,datetime,local]月[day,datetime,local]日[wkday,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] - </floater.string> - <text name="LabelItemNameTitle"> - 名前: - </text> - <text name="LabelItemDescTitle"> - 説明: - </text> - <text name="LabelCreatorTitle"> - 制作者: - </text> - <button label="プロフィール…" label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle"> - 所有者: - </text> - <button label="プロフィール…" label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle"> - 入手日時: - </text> - <text name="LabelAcquiredDate"> - 2006年5月24日水曜日 12:50:46 - </text> - <text name="OwnerLabel"> - あなた: - </text> - <check_box label="編集" name="CheckOwnerModify"/> - <check_box label="コピー" name="CheckOwnerCopy"/> - <check_box label="再販・譲渡" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel"> - 全員: - </text> - <check_box label="コピー" name="CheckEveryoneCopy"/> - <text name="GroupLabel"> - グループ: - </text> - <check_box label="共有" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel"> - 次の所有者: - </text> - <check_box label="編集" name="CheckNextOwnerModify"/> - <check_box label="コピー" name="CheckNextOwnerCopy"/> - <check_box label="再販・譲渡" name="CheckNextOwnerTransfer"/> - <check_box label="売り出し中" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="コピー" name="Copy"/> - <combo_box.item label="コンテンツ" name="Contents"/> - <combo_box.item label="オリジナル" name="Original"/> - </combo_box> - <spinner label="価格:" name="Edit Cost"/> - <text name="CurrencySymbol"> - L$ - </text> -</floater> diff --git a/indra/newview/skins/default/xui/ja/panel_main_inventory.xml b/indra/newview/skins/default/xui/ja/panel_main_inventory.xml index 9d78beb9da..a38492c6d1 100644 --- a/indra/newview/skins/default/xui/ja/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/ja/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> [ITEM_COUNT]個のアイテムと[CATEGORY_COUNT]個のフォルダーを取得しました。[FILTER] </panel.string> + <panel.string name="inventory_title">インベントリ</panel.string> <text name="ItemcountText"> アイテム: </text> diff --git a/indra/newview/skins/default/xui/pl/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/pl/floater_inventory_item_properties.xml deleted file mode 100644 index d2844e117f..0000000000 --- a/indra/newview/skins/default/xui/pl/floater_inventory_item_properties.xml +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> -<floater name="item properties" title="WŁAŚCIWOŚCI OBIEKTÓW W SZAFIE"> - <floater.string name="unknown"> - (nieznany) - </floater.string> - <floater.string name="public"> - (publiczny) - </floater.string> - <floater.string name="you_can"> - Ty możesz: - </floater.string> - <floater.string name="owner_can"> - Właściciel może: - </floater.string> - <text name="LabelItemNameTitle"> - Nazwa: - </text> - <text name="LabelItemDescTitle"> - Opis: - </text> - <text name="LabelCreatorTitle"> - Twórca: - </text> - <button label="Profil..." name="BtnCreator" /> - <text name="LabelOwnerTitle"> - Właściciel: - </text> - <button label="Profil..." name="BtnOwner" /> - <text name="LabelAcquiredTitle"> - Nabyte: - </text> - <text name="OwnerLabel"> - Ty: - </text> - <check_box label="Modyfikacja" name="CheckOwnerModify" /> - <check_box label="Kopiowanie" name="CheckOwnerCopy" /> - <check_box label="Transferowanie" name="CheckOwnerTransfer" /> - <text name="AnyoneLabel"> - Każdy: - </text> - <check_box label="Kopiowanie" name="CheckEveryoneCopy" /> - <text name="GroupLabel"> - Grupa: - </text> - <check_box label="Udostępnij" name="CheckShareWithGroup" /> - <text name="NextOwnerLabel"> - Nast. właściciel: - </text> - <check_box label="Modyfikacja" name="CheckNextOwnerModify" /> - <check_box label="Kopiowanie" name="CheckNextOwnerCopy" /> - <check_box label="Transferowanie" name="CheckNextOwnerTransfer" /> - <check_box label="Sprzedaż" name="CheckPurchase" /> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Kopia" name="Copy" /> - <combo_box.item label="Zawartość" name="Contents" /> - <combo_box.item label="Oryginał" name="Original" /> - </combo_box> - <spinner name="Edit Cost" label="Cena:" /> -</floater> diff --git a/indra/newview/skins/default/xui/pl/panel_main_inventory.xml b/indra/newview/skins/default/xui/pl/panel_main_inventory.xml index dc254e246f..1011c38378 100644 --- a/indra/newview/skins/default/xui/pl/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/pl/panel_main_inventory.xml @@ -6,6 +6,7 @@ <panel.string name="ItemcountCompleted"> [ITEM_COUNT] obiekty [FILTER] </panel.string> + <panel.string name="inventory_title">MOJA SZAFA</panel.string> <text name="ItemcountText"> Obiekty: </text> diff --git a/indra/newview/skins/default/xui/pt/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/pt/floater_inventory_item_properties.xml deleted file mode 100644 index 5f04c08531..0000000000 --- a/indra/newview/skins/default/xui/pt/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="PROPRIEDADES DE ITEM NO INVENTÁRIO"> - <floater.string name="unknown">(desconhecido)</floater.string> - <floater.string name="public">(público)</floater.string> - <floater.string name="you_can">Você pode:</floater.string> - <floater.string name="owner_can">Proprietário pode :</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Nome:</text> - <text name="LabelItemDescTitle">Descrição:</text> - <text name="LabelCreatorTitle">Criador:</text> - <button label="Perfil..." label_selected="" name="BtnCreator"/> - <text name="LabelOwnerTitle">Dono:</text> - <button label="Perfil..." label_selected="" name="BtnOwner"/> - <text name="LabelAcquiredTitle">Adquirido:</text> - <text name="LabelAcquiredDate">Qua Mai 24 12:50:46 2006</text> - <text name="OwnerLabel">Você:</text> - <check_box label="Editar" name="CheckOwnerModify"/> - <check_box label="Copiar" name="CheckOwnerCopy"/> - <check_box label="Revender" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">Todos:</text> - <check_box label="Cortar" name="CheckEveryoneCopy"/> - <text name="GroupLabel">Grupo:</text> - <check_box label="Compartilhar" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel" width="230">Próximo proprietário:</text> - <check_box label="Editar" name="CheckNextOwnerModify"/> - <check_box label="Copiar" name="CheckNextOwnerCopy"/> - <check_box label="Revender" name="CheckNextOwnerTransfer"/> - <check_box label="À venda" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Cortar" name="Copy"/> - <combo_box.item label="Conteúdo" name="Contents"/> - <combo_box.item label="Original" name="Original"/> - </combo_box> - <spinner label="Preço:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/pt/panel_main_inventory.xml b/indra/newview/skins/default/xui/pt/panel_main_inventory.xml index 009b5b3193..e0cf528468 100644 --- a/indra/newview/skins/default/xui/pt/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/pt/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> Itens [ITEM_COUNT] e Pastas [CATEGORY_COUNT] Reunidos [FILTER] </panel.string> + <panel.string name="inventory_title">INVENTÁRIO</panel.string> <text name="ItemcountText"> Itens: </text> diff --git a/indra/newview/skins/default/xui/ru/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/ru/floater_inventory_item_properties.xml deleted file mode 100644 index c988825756..0000000000 --- a/indra/newview/skins/default/xui/ru/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="СВОЙСТВА ПРЕДМЕТА"> - <floater.string name="unknown">(неизвестно)</floater.string> - <floater.string name="public">(публичное)</floater.string> - <floater.string name="you_can">Вы можете:</floater.string> - <floater.string name="owner_can">Владелец может:</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local], [day,datetime,local] [mth,datetime,local] [year,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Название:</text> - <text name="LabelItemDescTitle">Описание:</text> - <text name="LabelCreatorTitle">Создатель:</text> - <button label="Профиль…" name="BtnCreator"/> - <text name="LabelOwnerTitle">Владелец:</text> - <button label="Профиль…" name="BtnOwner"/> - <text name="LabelAcquiredTitle">Приобретено:</text> - <text name="LabelAcquiredDate">Ср 24 Май 12:50:46 2006</text> - <text name="OwnerLabel">Вы:</text> - <check_box label="Изменить" name="CheckOwnerModify"/> - <check_box label="Копировать" name="CheckOwnerCopy"/> - <check_box label="Перепродать" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">Все:</text> - <check_box label="Копировать" name="CheckEveryoneCopy"/> - <text name="GroupLabel">Группа:</text> - <check_box label="Поделиться" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel">Следующий владелец:</text> - <check_box label="Изменить" name="CheckNextOwnerModify"/> - <check_box label="Копировать" name="CheckNextOwnerCopy"/> - <check_box label="Перепродать" name="CheckNextOwnerTransfer"/> - <check_box label="Для продажи" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Копировать" name="Copy"/> - <combo_box.item label="Содержимое" name="Contents"/> - <combo_box.item label="Оригинал" name="Original"/> - </combo_box> - <spinner label="Цена:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/ru/panel_main_inventory.xml b/indra/newview/skins/default/xui/ru/panel_main_inventory.xml index f2502bf6d3..b473fb8f98 100644 --- a/indra/newview/skins/default/xui/ru/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/ru/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> Выборка [ITEM_COUNT] предметов и [CATEGORY_COUNT] папок [FILTER] </panel.string> + <panel.string name="inventory_title">ИНВЕНТАРЬ</panel.string> <text name="ItemcountText"> Вещи: </text> diff --git a/indra/newview/skins/default/xui/tr/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/tr/floater_inventory_item_properties.xml deleted file mode 100644 index c6a5515c6e..0000000000 --- a/indra/newview/skins/default/xui/tr/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="ENVANTER ÖGESİ ÖZELLİKLERİ"> - <floater.string name="unknown">(bilinmiyor)</floater.string> - <floater.string name="public">(kamuya açık)</floater.string> - <floater.string name="you_can">Şunu yapabilirsiniz:</floater.string> - <floater.string name="owner_can">Sahip şunu yapabilir:</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</floater.string> - <text name="LabelItemNameTitle">Ad:</text> - <text name="LabelItemDescTitle">Açıklama:</text> - <text name="LabelCreatorTitle">Oluşturan:</text> - <button label="Profil..." name="BtnCreator"/> - <text name="LabelOwnerTitle">Sahip:</text> - <button label="Profil..." name="BtnOwner"/> - <text name="LabelAcquiredTitle">Alınan:</text> - <text name="LabelAcquiredDate">24 Mayıs Çarş 12:50:46 2006</text> - <text name="OwnerLabel">Siz:</text> - <check_box label="Düzenle" name="CheckOwnerModify"/> - <check_box label="Kopyala" name="CheckOwnerCopy"/> - <check_box label="Tekrar Sat" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">Herkes:</text> - <check_box label="Kopyala" name="CheckEveryoneCopy"/> - <text name="GroupLabel">Grup:</text> - <check_box label="Paylaş" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel">Sonraki sahip:</text> - <check_box label="Düzenle" name="CheckNextOwnerModify"/> - <check_box label="Kopyala" name="CheckNextOwnerCopy"/> - <check_box label="Tekrar Sat" name="CheckNextOwnerTransfer"/> - <check_box label="Satılık" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="Kopyala" name="Copy"/> - <combo_box.item label="İçerik" name="Contents"/> - <combo_box.item label="Orijinal" name="Original"/> - </combo_box> - <spinner label="Fiyat:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/tr/panel_main_inventory.xml b/indra/newview/skins/default/xui/tr/panel_main_inventory.xml index a11fd98b9a..7e98078635 100644 --- a/indra/newview/skins/default/xui/tr/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/tr/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> [ITEM_COUNT] Öğe ve [CATEGORY_COUNT] Klasör Alındı [FILTER] </panel.string> + <panel.string name="inventory_title">ENVANTER</panel.string> <text name="ItemcountText"> Ögeler: </text> diff --git a/indra/newview/skins/default/xui/zh/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/zh/floater_inventory_item_properties.xml deleted file mode 100644 index 4f17b96579..0000000000 --- a/indra/newview/skins/default/xui/zh/floater_inventory_item_properties.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="item properties" title="收納區物品屬性"> - <floater.string name="unknown">(未知)</floater.string> - <floater.string name="public">(公開)</floater.string> - <floater.string name="you_can">你可以:</floater.string> - <floater.string name="owner_can">所有人可以:</floater.string> - <floater.string name="acquiredDate">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</floater.string> - <text name="LabelItemNameTitle">名稱:</text> - <text name="LabelItemDescTitle">描述:</text> - <text name="LabelCreatorTitle">創造者:</text> - <button label="檔案..." name="BtnCreator"/> - <text name="LabelOwnerTitle">所有人:</text> - <button label="檔案..." name="BtnOwner"/> - <text name="LabelAcquiredTitle">取得於:</text> - <text name="LabelAcquiredDate">Wed May 24 12:50:46 2006</text> - <text name="OwnerLabel">你:</text> - <check_box label="編輯" name="CheckOwnerModify"/> - <check_box label="恚庨" name="CheckOwnerCopy"/> - <check_box label="轉售" name="CheckOwnerTransfer"/> - <text name="AnyoneLabel">任何人:</text> - <check_box label="恚庨" name="CheckEveryoneCopy"/> - <text name="GroupLabel">群組:</text> - <check_box label="分享" name="CheckShareWithGroup"/> - <text name="NextOwnerLabel">下一個所有人:</text> - <check_box label="編輯" name="CheckNextOwnerModify"/> - <check_box label="恚庨" name="CheckNextOwnerCopy"/> - <check_box label="轉售" name="CheckNextOwnerTransfer"/> - <check_box label="出售" name="CheckPurchase"/> - <combo_box name="ComboBoxSaleType"> - <combo_box.item label="複製" name="Copy"/> - <combo_box.item label="內容" name="Contents"/> - <combo_box.item label="原件" name="Original"/> - </combo_box> - <spinner label="價格:" name="Edit Cost"/> - <text name="CurrencySymbol">L$</text> -</floater> diff --git a/indra/newview/skins/default/xui/zh/panel_main_inventory.xml b/indra/newview/skins/default/xui/zh/panel_main_inventory.xml index 1a28f4c3b5..9ffa9323cc 100644 --- a/indra/newview/skins/default/xui/zh/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/zh/panel_main_inventory.xml @@ -9,6 +9,7 @@ <panel.string name="ItemcountUnknown"> 擷取了[ITEM_COUNT]個物項及[CATEGORY_COUNT]個資料夾[FILTER] </panel.string> + <panel.string name="inventory_title">收納區</panel.string> <text name="ItemcountText"> 物品: </text> |