From a62f5bfac1e55455c67e1c36eb388af99795dab6 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Sat, 14 Oct 2023 02:40:59 +0300 Subject: SL-20232 Allow deletion of worn items #1 --- indra/newview/llinventoryfunctions.cpp | 87 +++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 12 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 4aeacae6ed..109dffae6b 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -682,17 +682,17 @@ BOOL get_can_item_be_worn(const LLUUID& id) return FALSE; } -BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id) +bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn) { if (!model) { - return FALSE; + return false; } // Can't delete an item that's in the library. if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID())) { - return FALSE; + return false; } // Disable delete from COF folder; have users explicitly choose "detach/take off", @@ -701,20 +701,20 @@ BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id) { if (get_is_item_worn(id)) { - return FALSE; + return false; } } const LLInventoryObject *obj = model->getItem(id); if (obj && obj->getIsLinkType()) { - return TRUE; + return true; } - if (get_is_item_worn(id)) + if (check_worn && get_is_item_worn(id)) { - return FALSE; + return false; } - return TRUE; + return true; } bool get_is_item_editable(const LLUUID& inv_item_id) @@ -2759,7 +2759,7 @@ bool LLFindNonRemovableObjects::operator()(LLInventoryCategory* cat, LLInventory { if (item) { - return !get_is_item_removable(&gInventory, item->getUUID()); + return !get_is_item_removable(&gInventory, item->getUUID(), true); } if (cat) { @@ -3024,6 +3024,8 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); bool marketplacelistings_item = false; + bool worn_item = false; + bool needs_replacement = false; LLAllDescendentsPassedFilter f; for (std::set::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) { @@ -3037,9 +3039,32 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root marketplacelistings_item = true; break; } + if (get_is_item_worn(viewModel->getUUID())) + { + worn_item = true; + LLWearableType::EType type = viewModel->getWearableType(); + if (type == LLWearableType::WT_SHAPE + || type == LLWearableType::WT_SKIN + || type == LLWearableType::WT_HAIR + || type == LLWearableType::WT_EYES) + { + needs_replacement = true; + break; + } + } } // Fall through to the generic confirmation if the user choose to ignore the specialized one - if ( (!f.allDescendentsPassedFilter()) && !marketplacelistings_item && (!LLNotifications::instance().getIgnored("DeleteFilteredItems")) ) + if (needs_replacement) + { + LLNotificationsUtil::add("CantDeleteRequiredClothing"); + } + else if (worn_item) + { + LLSD payload; + payload["has_worn"] = true; + LLNotificationsUtil::add("DeleteWornItems", LLSD(), payload, boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); + } + else if ( (!f.allDescendentsPassedFilter()) && !marketplacelistings_item && (!LLNotifications::instance().getIgnored("DeleteFilteredItems")) ) { LLNotificationsUtil::add("DeleteFilteredItems", LLSD(), LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); } @@ -3356,17 +3381,55 @@ void LLInventoryAction::removeItemFromDND(LLFolderView* root) } } } - void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option == 0 && !root.isDead() && !root.get()->isDead()) { + bool has_worn = notification["payload"]["has_worn"].asBoolean(); LLFolderView* folder_root = root.get(); //Need to remove item from DND before item is removed from root folder view //because once removed from root folder view the item is no longer a selected item removeItemFromDND(folder_root); - folder_root->removeSelectedItems(); + + // removeSelectedItems will change selection, collect worn items beforehand + uuid_vec_t worn; + if (has_worn) + { + //Get selected items + LLFolderView::selected_items_t selectedItems = folder_root->getSelectedItems(); + + //If user is in DND and deletes item, make sure the notification is not displayed by removing the notification + //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. + for (LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + LLObjectBridge* view_model = dynamic_cast((*it)->getViewModelItem()); + + if (view_model && get_is_item_worn(view_model->getUUID())) + { + worn.push_back(view_model->getUUID()); + } + } + } + + // removeSelectedItems will check if items are worn before deletion, + // don't 'unwear' yet to prevent a race condition from unwearing + // and removing simultaneously + folder_root->removeSelectedItems(); + + // unwear then delete the rest + if (!worn.empty()) + { + // should fire once after every item gets detached + LLAppearanceMgr::instance().removeItemsFromAvatar(worn, + [worn]() + { + for (const LLUUID& id : worn) + { + remove_inventory_item(id, NULL); + } + }); + } // Update the marketplace listings that have been affected by the operation updateMarketplaceFolders(); -- cgit v1.2.3 From 813b0e7675f0f9fb682831bdf60319f8b584b882 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Sat, 14 Oct 2023 12:14:51 +0300 Subject: SL-20232 Allow deletion of worn items #2 --- indra/newview/llinventoryfunctions.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 109dffae6b..5e702ef755 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -3024,7 +3024,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); bool marketplacelistings_item = false; - bool worn_item = false; + bool has_worn = false; bool needs_replacement = false; LLAllDescendentsPassedFilter f; for (std::set::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) @@ -3041,7 +3041,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root } if (get_is_item_worn(viewModel->getUUID())) { - worn_item = true; + has_worn = true; LLWearableType::EType type = viewModel->getWearableType(); if (type == LLWearableType::WT_SHAPE || type == LLWearableType::WT_SKIN @@ -3058,7 +3058,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { LLNotificationsUtil::add("CantDeleteRequiredClothing"); } - else if (worn_item) + else if (has_worn) { LLSD payload; payload["has_worn"] = true; @@ -3411,6 +3411,7 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con } } } + // TODO: collect worn items from content and folders that neede to be deleted after that // removeSelectedItems will check if items are worn before deletion, // don't 'unwear' yet to prevent a race condition from unwearing -- cgit v1.2.3 From ce83f77e7a5f32ac00b0a563fcf68ff98957f684 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 17 Oct 2023 00:34:00 +0300 Subject: SL-20232 Allow deletion of folders with worn content in tree view #3 --- indra/newview/llinventoryfunctions.cpp | 96 +++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 12 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 5e702ef755..849f641758 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -579,9 +579,8 @@ BOOL get_is_parent_to_worn_item(const LLUUID& id) return FALSE; } -BOOL get_is_item_worn(const LLUUID& id) +BOOL get_is_item_worn(const LLUUID& id, const LLViewerInventoryItem* item) { - const LLViewerInventoryItem* item = gInventory.getItem(id); if (!item) return FALSE; @@ -619,6 +618,21 @@ BOOL get_is_item_worn(const LLUUID& id) return FALSE; } +BOOL get_is_item_worn(const LLUUID& id) +{ + const LLViewerInventoryItem* item = gInventory.getItem(id); + return get_is_item_worn(item); +} + +BOOL get_is_item_worn(const LLViewerInventoryItem* item) +{ + if (!item) + { + return FALSE; + } + return get_is_item_worn(item->getUUID(), item); +} + BOOL get_can_item_be_worn(const LLUUID& id) { const LLViewerInventoryItem* item = gInventory.getItem(id); @@ -3034,15 +3048,47 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root folder->applyFunctorRecursively(f); } LLFolderViewModelItemInventory * viewModel = dynamic_cast((*it)->getViewModelItem()); - if (viewModel && gInventory.isObjectDescendentOf(viewModel->getUUID(), marketplacelistings_id)) + LLUUID obj_id = viewModel->getUUID(); + if (viewModel && gInventory.isObjectDescendentOf(obj_id, marketplacelistings_id)) { marketplacelistings_item = true; break; } - if (get_is_item_worn(viewModel->getUUID())) + + LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id); + if (cat) + { + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + + gInventory.collectDescendents(obj_id, categories, items, FALSE); + + for (LLInventoryModel::item_array_t::value_type& item : items) + { + if (get_is_item_worn(item)) + { + has_worn = true; + LLWearableType::EType type = item->getWearableType(); + if (type == LLWearableType::WT_SHAPE + || type == LLWearableType::WT_SKIN + || type == LLWearableType::WT_HAIR + || type == LLWearableType::WT_EYES) + { + needs_replacement = true; + break; + } + } + } + if (needs_replacement) + { + break; + } + } + LLViewerInventoryItem* item = gInventory.getItem(obj_id); + if (item && get_is_item_worn(item)) { has_worn = true; - LLWearableType::EType type = viewModel->getWearableType(); + LLWearableType::EType type = item->getWearableType(); if (type == LLWearableType::WT_SHAPE || type == LLWearableType::WT_SKIN || type == LLWearableType::WT_HAIR @@ -3381,6 +3427,7 @@ void LLInventoryAction::removeItemFromDND(LLFolderView* root) } } } + void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); @@ -3394,6 +3441,7 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con // removeSelectedItems will change selection, collect worn items beforehand uuid_vec_t worn; + uuid_vec_t deletion_list; if (has_worn) { //Get selected items @@ -3403,18 +3451,42 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. for (LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) { - LLObjectBridge* view_model = dynamic_cast((*it)->getViewModelItem()); + LLFolderViewModelItemInventory* viewModel = dynamic_cast((*it)->getViewModelItem()); + + LLUUID obj_id = viewModel->getUUID(); + LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id); + bool cat_has_worn = false; + if (cat) + { + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + + gInventory.collectDescendents(obj_id, categories, items, FALSE); - if (view_model && get_is_item_worn(view_model->getUUID())) + for (LLInventoryModel::item_array_t::value_type& item : items) + { + if (get_is_item_worn(item)) + { + worn.push_back(item->getUUID()); + cat_has_worn = true; + } + } + if (cat_has_worn) + { + deletion_list.push_back(obj_id); + } + } + LLViewerInventoryItem* item = gInventory.getItem(obj_id); + if (item && get_is_item_worn(item)) { - worn.push_back(view_model->getUUID()); + worn.push_back(obj_id); + deletion_list.push_back(obj_id); } } } - // TODO: collect worn items from content and folders that neede to be deleted after that // removeSelectedItems will check if items are worn before deletion, - // don't 'unwear' yet to prevent a race condition from unwearing + // don't 'unwear' yet to prevent race conditions from unwearing // and removing simultaneously folder_root->removeSelectedItems(); @@ -3423,9 +3495,9 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con { // should fire once after every item gets detached LLAppearanceMgr::instance().removeItemsFromAvatar(worn, - [worn]() + [deletion_list]() { - for (const LLUUID& id : worn) + for (const LLUUID& id : deletion_list) { remove_inventory_item(id, NULL); } -- cgit v1.2.3 From e233e4fb50e513b58f4452086182b60b25440fac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 17 Oct 2023 01:35:47 +0300 Subject: SL-20232 Allow deletion of worn items #5 --- indra/newview/llinventoryfunctions.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 849f641758..c2cbf4b8ac 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -3441,7 +3441,8 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con // removeSelectedItems will change selection, collect worn items beforehand uuid_vec_t worn; - uuid_vec_t deletion_list; + uuid_vec_t item_deletion_list; + uuid_vec_t cat_deletion_list; if (has_worn) { //Get selected items @@ -3473,14 +3474,14 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con } if (cat_has_worn) { - deletion_list.push_back(obj_id); + cat_deletion_list.push_back(obj_id); } } LLViewerInventoryItem* item = gInventory.getItem(obj_id); if (item && get_is_item_worn(item)) { worn.push_back(obj_id); - deletion_list.push_back(obj_id); + item_deletion_list.push_back(obj_id); } } } @@ -3495,12 +3496,16 @@ void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, con { // should fire once after every item gets detached LLAppearanceMgr::instance().removeItemsFromAvatar(worn, - [deletion_list]() + [item_deletion_list, cat_deletion_list]() { - for (const LLUUID& id : deletion_list) + for (const LLUUID& id : item_deletion_list) { remove_inventory_item(id, NULL); } + for (const LLUUID& id : cat_deletion_list) + { + remove_inventory_category(id, NULL); + } }); } -- cgit v1.2.3 From aff01e2fd4fb5475eb42ce58d1ad997341cb7a94 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 10 Nov 2023 23:36:33 +0200 Subject: SL-20575 Fix some deletion issues - Fixes 'cut' checks for gallery - Checking if large folder is deletable should be faster now - For large flat root folders eliminated delay of looking for cof Might be a good idea to start cashing isItemRemovable if category version hasn't changed. --- indra/newview/llinventoryfunctions.cpp | 76 +++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index c2cbf4b8ac..5d6bf8ab4b 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -621,7 +621,7 @@ BOOL get_is_item_worn(const LLUUID& id, const LLViewerInventoryItem* item) BOOL get_is_item_worn(const LLUUID& id) { const LLViewerInventoryItem* item = gInventory.getItem(id); - return get_is_item_worn(item); + return get_is_item_worn(id, item); } BOOL get_is_item_worn(const LLViewerInventoryItem* item) @@ -711,20 +711,20 @@ bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool // Disable delete from COF folder; have users explicitly choose "detach/take off", // unless the item is not worn but in the COF (i.e. is bugged). - if (LLAppearanceMgr::instance().getIsProtectedCOFItem(id)) + const LLViewerInventoryItem* obj = model->getItem(id); + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(obj)) { - if (get_is_item_worn(id)) + if (get_is_item_worn(id, obj)) { return false; } } - const LLInventoryObject *obj = model->getItem(id); if (obj && obj->getIsLinkType()) { return true; } - if (check_worn && get_is_item_worn(id)) + if (check_worn && get_is_item_worn(id, obj)) { return false; } @@ -819,6 +819,72 @@ BOOL get_is_category_removable(const LLInventoryModel* model, const LLUUID& id) return TRUE; } +bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUID& folder_id, bool check_worn) +{ + if (!get_is_category_removable(model, folder_id)) + { + return false; + } + + const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (mp_id.notNull() && gInventory.isObjectDescendentOf(folder_id, mp_id)) + { + return false; + } + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + model->collectDescendents( + folder_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + + for (LLInventoryModel::item_array_t::value_type& item : item_array) + { + // Disable delete from COF folder; have users explicitly choose "detach/take off", + // unless the item is not worn but in the COF (i.e. is bugged). + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) + { + if (get_is_item_worn(item)) + { + return false; + } + } + + if (item && item->getIsLinkType()) + { + return true; + } + if (check_worn && get_is_item_worn(item)) + { + return false; + } + } + + const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); + LLViewerInventoryCategory* outfit_linked_category = base_outfit_link ? base_outfit_link->getLinkedCategory() : nullptr; + for (LLInventoryModel::cat_array_t::value_type& cat : cat_array) + { + const LLFolderType::EType folder_type = cat->getPreferredType(); + if (LLFolderType::lookupIsProtectedType(folder_type)) + { + return false; + } + + // Can't delete the outfit that is currently being worn. + if (folder_type == LLFolderType::FT_OUTFIT) + { + if (cat == outfit_linked_category) + { + return false; + } + } + } + + return true; +} + BOOL get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id) { if (!model) -- cgit v1.2.3 From 7ff358efc46a07c1ed3bc845c693d67c5eb95b69 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 15 Nov 2023 21:01:05 +0200 Subject: SL-20575 Fixed premature exit --- indra/newview/llinventoryfunctions.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 5d6bf8ab4b..a260b62c69 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -844,21 +844,20 @@ bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUI { // Disable delete from COF folder; have users explicitly choose "detach/take off", // unless the item is not worn but in the COF (i.e. is bugged). - if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) + if (item) { - if (get_is_item_worn(item)) + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) { - return false; + if (get_is_item_worn(item)) + { + return false; + } } - } - if (item && item->getIsLinkType()) - { - return true; - } - if (check_worn && get_is_item_worn(item)) - { - return false; + if (check_worn && !item->getIsLinkType() && get_is_item_worn(item)) + { + return false; + } } } -- cgit v1.2.3 From fda96e36806dab544709ac87156949c3accc5cf8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 15 Nov 2023 22:15:03 +0200 Subject: SL-20575 When opening menu cache results and spread load over frames --- indra/newview/llinventoryfunctions.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'indra/newview/llinventoryfunctions.cpp') diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index a260b62c69..97568fede1 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -840,23 +840,26 @@ bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUI item_array, LLInventoryModel::EXCLUDE_TRASH); - for (LLInventoryModel::item_array_t::value_type& item : item_array) + if (check_worn) { - // Disable delete from COF folder; have users explicitly choose "detach/take off", - // unless the item is not worn but in the COF (i.e. is bugged). - if (item) + for (LLInventoryModel::item_array_t::value_type& item : item_array) { - if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) + // Disable delete/cut from COF folder; have users explicitly choose "detach/take off", + // unless the item is not worn but in the COF (i.e. is bugged). + if (item) { - if (get_is_item_worn(item)) + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) { - return false; + if (get_is_item_worn(item)) + { + return false; + } } - } - if (check_worn && !item->getIsLinkType() && get_is_item_worn(item)) - { - return false; + if (!item->getIsLinkType() && get_is_item_worn(item)) + { + return false; + } } } } -- cgit v1.2.3