Skip to content

#3757 Allow subfolders in "My Outfits" #3890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions indra/llcommon/lluuid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,6 @@ void LLUUID::toString(std::string& out) const
(U8)(mData[15]));
}

// *TODO: deprecate
void LLUUID::toString(char* out) const
{
std::string buffer;
toString(buffer);
strcpy(out, buffer.c_str()); /* Flawfinder: ignore */
}

void LLUUID::toCompressedString(std::string& out) const
{
char bytes[UUID_BYTES + 1];
Expand All @@ -190,13 +182,6 @@ void LLUUID::toCompressedString(std::string& out) const
out.assign(bytes, UUID_BYTES);
}

// *TODO: deprecate
void LLUUID::toCompressedString(char* out) const
{
memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */
out[UUID_BYTES] = '\0';
}

std::string LLUUID::getString() const
{
return asString();
Expand Down
2 changes: 0 additions & 2 deletions indra/llcommon/lluuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,7 @@ class LL_COMMON_API LLUUID
friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid);
friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid);

void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0)
void toString(std::string& out) const;
void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0)
void toCompressedString(std::string& out) const;

std::string asString() const;
Expand Down
2 changes: 1 addition & 1 deletion indra/newview/llappearancelistener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ void LLAppearanceListener::getOutfitsList(LLSD const &data)
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;

LLIsType is_category(LLAssetType::AT_CATEGORY);
LLIsFolderType is_category(LLFolderType::FT_OUTFIT);
gInventory.collectDescendentsIf(outfits_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, is_category);

response["outfits"] = llsd::toMap(cat_array,
Expand Down
269 changes: 212 additions & 57 deletions indra/newview/llinventorybridge.cpp

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions indra/newview/llinventorybridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class LLFolderBridge : public LLInvFVBridge
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);
void dropToMyOutfitsSubfolder(LLInventoryCategory* inv_cat, const LLUUID& dest, LLFolderType::EType preferred_type, LLPointer<LLInventoryCallback> cb = NULL);

//--------------------------------------------------------------------
// Messy hacks for handling folder options
Expand Down
39 changes: 39 additions & 0 deletions indra/newview/llinventoryfunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2493,6 +2493,40 @@ bool can_share_item(const LLUUID& item_id)

return can_share;
}

EMyOutfitsSubfolderType myoutfit_object_subfolder_type(
LLInventoryModel* model,
const LLUUID& obj_id,
const LLUUID& my_outfits_id)
{
if (obj_id == my_outfits_id) return MY_OUTFITS_NO;

const LLViewerInventoryCategory* test_cat = model->getCategory(obj_id);
if (test_cat->getPreferredType() == LLFolderType::FT_OUTFIT)
{
return MY_OUTFITS_OUTFIT;
}
while (test_cat)
{
if (test_cat->getPreferredType() == LLFolderType::FT_OUTFIT)
{
return MY_OUTFITS_SUBOUTFIT;
}

const LLUUID& parent_id = test_cat->getParentUUID();
if (parent_id.isNull())
{
return MY_OUTFITS_NO;
}
if (parent_id == my_outfits_id)
{
return MY_OUTFITS_SUBFOLDER;
}
test_cat = model->getCategory(parent_id);
}

return MY_OUTFITS_NO;
}
///----------------------------------------------------------------------------
/// LLMarketplaceValidator implementations
///----------------------------------------------------------------------------
Expand Down Expand Up @@ -2621,6 +2655,11 @@ bool LLInventoryCollectFunctor::itemTransferCommonlyAllowed(const LLInventoryIte
return false;
}

bool LLIsFolderType::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
{
return cat && cat->getPreferredType() == mType;
}

bool LLIsType::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
{
if(mType == LLAssetType::AT_CATEGORY)
Expand Down
30 changes: 30 additions & 0 deletions indra/newview/llinventoryfunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& i
std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id);
bool can_share_item(const LLUUID& item_id);

enum EMyOutfitsSubfolderType
{
MY_OUTFITS_NO,
MY_OUTFITS_SUBFOLDER,
MY_OUTFITS_OUTFIT,
MY_OUTFITS_SUBOUTFIT,
};
EMyOutfitsSubfolderType myoutfit_object_subfolder_type(
LLInventoryModel* model,
const LLUUID& obj_id,
const LLUUID& my_outfits_id);

/** Miscellaneous global functions
** **
*******************************************************************************/
Expand Down Expand Up @@ -234,6 +246,24 @@ class LLLinkedItemIDMatches : public LLInventoryCollectFunctor
// the type is the type passed in during construction.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class LLIsFolderType : public LLInventoryCollectFunctor
{
public:
LLIsFolderType(LLFolderType::EType type) : mType(type) {}
virtual ~LLIsFolderType() {}
virtual bool operator()(LLInventoryCategory* cat,
LLInventoryItem* item);
protected:
LLFolderType::EType mType;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLIsType
//
// Implementation of a LLInventoryCollectFunctor which returns true if
// the type is the type passed in during construction.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class LLIsType : public LLInventoryCollectFunctor
{
public:
Expand Down
116 changes: 109 additions & 7 deletions indra/newview/llinventorygallery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ static LLPanelInjector<LLInventoryGallery> t_inventory_gallery("inventory_galler
const S32 GALLERY_ITEMS_PER_ROW_MIN = 2;
const S32 FAST_LOAD_THUMBNAIL_TRSHOLD = 50; // load folders below this value immediately


// 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);
void dropToMyOutfitsSubfolder(LLInventoryCategory* inv_cat, const LLUUID& dest_id, LLFolderType::EType preferred_type);

class LLGalleryPanel: public LLPanel
{
Expand Down Expand Up @@ -3745,7 +3747,12 @@ bool dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat,
U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit");
if (is_movable && move_is_into_outfit)
{
if (dest_id == my_outifts_id)
if ((inv_cat->getPreferredType() != LLFolderType::FT_NONE) && (inv_cat->getPreferredType() != LLFolderType::FT_OUTFIT))
{
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
is_movable = false;
}
else if (dest_id == my_outifts_id)
{
if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings)
{
Expand All @@ -3762,13 +3769,39 @@ bool dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat,
is_movable = false;
}
}
else if (dest_cat && dest_cat->getPreferredType() == LLFolderType::FT_NONE)
else if (!dest_cat)
{
is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT));
is_movable = false;
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
}
else
{
is_movable = false;
EMyOutfitsSubfolderType dest_res = myoutfit_object_subfolder_type(model, dest_id, my_outifts_id);
EMyOutfitsSubfolderType inv_res = myoutfit_object_subfolder_type(model, cat_id, my_outifts_id);
if ((dest_res == MY_OUTFITS_OUTFIT || dest_res == MY_OUTFITS_SUBOUTFIT) && inv_res == MY_OUTFITS_OUTFIT)
{
is_movable = false;
tooltip_msg = LLTrans::getString("TooltipCantMoveOutfitIntoOutfit");
}
else if ((dest_res == MY_OUTFITS_OUTFIT || dest_res == MY_OUTFITS_SUBOUTFIT) && inv_res == MY_OUTFITS_SUBFOLDER)
{
is_movable = false;
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
}
else if (dest_res == MY_OUTFITS_SUBFOLDER && inv_res == MY_OUTFITS_SUBOUTFIT)
{
is_movable = false;
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
}
else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear))
{
is_movable = true;
}
else
{
is_movable = false;
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
}
}
}
if (is_movable && move_is_into_current_outfit && is_link)
Expand Down Expand Up @@ -3894,9 +3927,70 @@ bool dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat,

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);
EMyOutfitsSubfolderType inv_res = myoutfit_object_subfolder_type(model, cat_id, my_outifts_id);
if (inv_res == MY_OUTFITS_SUBFOLDER || inv_res == MY_OUTFITS_OUTFIT)
{
gInventory.changeCategoryParent(
(LLViewerInventoryCategory*)inv_cat,
dest_id,
move_is_into_trash);
}
else
{
// Category can contains objects,
// create a new folder and populate it with links to original objects
dropToMyOutfits(inv_cat);
}
}
else if (move_is_into_my_outfits)
{
EMyOutfitsSubfolderType dest_res = myoutfit_object_subfolder_type(model, dest_id, my_outifts_id);
EMyOutfitsSubfolderType inv_res = myoutfit_object_subfolder_type(model, cat_id, my_outifts_id);
switch (inv_res)
{
case MY_OUTFITS_NO:
// Moning from outside outfits into outfits
if (dest_res == MY_OUTFITS_SUBFOLDER)
{
// turn it into outfit
dropToMyOutfitsSubfolder(inv_cat, dest_id, LLFolderType::FT_OUTFIT);
}
else
{
dropToMyOutfitsSubfolder(inv_cat, dest_id, LLFolderType::FT_NONE);
}
break;
case MY_OUTFITS_SUBFOLDER:
case MY_OUTFITS_OUTFIT:
// only permit moving subfodlers and outfits into other subfolders
if (dest_res == MY_OUTFITS_SUBFOLDER)
{
gInventory.changeCategoryParent(
(LLViewerInventoryCategory*)inv_cat,
dest_id,
move_is_into_trash);
}
else
{
assert(false); // mot permitted, shouldn't have accepted
}
break;
case MY_OUTFITS_SUBOUTFIT:
if (dest_res == MY_OUTFITS_SUBOUTFIT || dest_res == MY_OUTFITS_OUTFIT)
{
gInventory.changeCategoryParent(
(LLViewerInventoryCategory*)inv_cat,
dest_id,
move_is_into_trash);
}
else
{
assert(false); // mot permitted, shouldn't have accepted
}
break;
default:
break;
}
}
// if target is current outfit folder we use link
else if (move_is_into_current_outfit &&
Expand Down Expand Up @@ -4041,3 +4135,11 @@ void dropToMyOutfits(LLInventoryCategory* inv_cat)
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());
}

void dropToMyOutfitsSubfolder(LLInventoryCategory* inv_cat, const LLUUID &dest_id, LLFolderType::EType preferred_type)
{
// 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, preferred_type, inv_cat->getName(), func, inv_cat->getThumbnailUUID());
}
25 changes: 19 additions & 6 deletions indra/newview/llinventorygallerymenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,9 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men
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));
const LLUUID my_outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
bool is_outfits= (selected_id == my_outfits);
bool is_in_outfits = is_outfits || gInventory.isObjectDescendentOf(selected_id, my_outfits);
bool is_in_favorites = gInventory.isObjectDescendentOf(selected_id, gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE));
//bool is_favorites= (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE));

Expand Down Expand Up @@ -725,7 +727,7 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men
}
else
{
if (is_agent_inventory && !is_inbox && !is_cof && !is_in_favorites && !is_outfits)
if (is_agent_inventory && !is_inbox && !is_cof && !is_in_favorites && !is_outfits && !is_in_outfits)
{
LLViewerInventoryCategory* category = gInventory.getCategory(selected_id);
if (!category || !LLFriendCardsManager::instance().isCategoryInFriendFolder(category))
Expand Down Expand Up @@ -769,15 +771,26 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men
items.push_back(std::string("upload_def"));
}

if(is_outfits && !isRootFolder())
if(is_outfits)
{
items.push_back(std::string("New Outfit"));
EMyOutfitsSubfolderType res = myoutfit_object_subfolder_type(&gInventory, selected_id, my_outfits);
if (res != MY_OUTFITS_OUTFIT && res != MY_OUTFITS_SUBOUTFIT)
{
items.push_back(std::string("New Outfit"));
}
items.push_back(std::string("New Outfit Folder"));
items.push_back(std::string("Delete"));
items.push_back(std::string("Rename"));
if (!get_is_category_and_children_removable(&gInventory, selected_id, false))
{
disabled_items.push_back(std::string("Delete"));
}
}

items.push_back(std::string("Subfolder Separator"));
if (!is_system_folder && !isRootFolder())
if (!is_system_folder && !isRootFolder() && !is_outfits)
{
if(has_children && (folder_type != LLFolderType::FT_OUTFIT))
if(has_children && (folder_type != LLFolderType::FT_OUTFIT) && !is_in_outfits)
{
items.push_back(std::string("Ungroup folder items"));
}
Expand Down
5 changes: 3 additions & 2 deletions indra/newview/llinventorymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,8 @@ void LLInventoryModel::createNewCategory(const LLUUID& parent_id,
return;
}

if (preferred_type != LLFolderType::FT_NONE)
if (preferred_type != LLFolderType::FT_NONE
&& preferred_type != LLFolderType::FT_OUTFIT)
{
// Ultimately this should only be done for non-singleton
// types. Requires back-end changes to guarantee that others
Expand Down Expand Up @@ -3525,7 +3526,7 @@ bool LLInventoryModel::saveToFile(const std::string& filename,

fileXML.close();

LL_INFOS(LOG_INV) << "Inventory saved: " << cat_count << " categories, " << it_count << " items." << LL_ENDL;
LL_INFOS(LOG_INV) << "Inventory saved: " << (S32)cat_count << " categories, " << (S32)it_count << " items." << LL_ENDL;
}
catch (...)
{
Expand Down
Loading
Loading