/**
 * @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;
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);

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),
      mLoadThumbnailsImmediately(true),
      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;

    // Avoid loading too many items.
    // Intent is for small folders to display all content fast
    // and for large folders to load content mostly as needed
    // Todo: ideally needs to unload images outside visible area
    mLoadThumbnailsImmediately = mItemsAddedCount < FAST_LOAD_THUMBNAIL_TRSHOLD;

    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);

    mLoadThumbnailsImmediately = mItemsAddedCount < FAST_LOAD_THUMBNAIL_TRSHOLD;

    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->setLoadImmediately(mLoadThumbnailsImmediately);
    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]->setLoadImmediately(mLoadThumbnailsImmediately);
        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] && !mItemMap[id]->isHidden())
            {
                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 is_category_removable(const LLUUID& folder_id, bool check_worn)
{
    if (!get_is_category_removable(&gInventory, folder_id))
    {
        return false;
    }

    // check children
    LLInventoryModel::cat_array_t* cat_array;
    LLInventoryModel::item_array_t* item_array;
    gInventory.getDirectDescendentsOf(folder_id, cat_array, item_array);

    for (LLInventoryModel::item_array_t::value_type& item : *item_array)
    {
        if (!get_is_item_removable(&gInventory, item->getUUID(), check_worn))
        {
            return false;
        }
    }

    for (LLInventoryModel::cat_array_t::value_type& cat : *cat_array)
    {
        if (!is_category_removable(cat->getUUID(), check_worn))
        {
            return false;
        }
    }

    const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
    if (mp_id.notNull() && gInventory.isObjectDescendentOf(folder_id, mp_id))
    {
        return false;
    }

    return true;
}

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_and_children_removable(&gInventory, id, true))
            {
                return FALSE;
            }
        }
        else if (!get_is_item_removable(&gInventory, id, true))
        {
            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)
    {
        bool has_worn = notification["payload"]["has_worn"].asBoolean();
        uuid_vec_t worn;
        uuid_vec_t item_deletion_list;
        uuid_vec_t cat_deletion_list;
        for (const LLUUID& obj_id : selected_ids)
        {
            LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id);
            if (cat)
            {
                bool cat_has_worn = false;
                if (has_worn)
                {
                    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))
                        {
                            worn.push_back(item->getUUID());
                            cat_has_worn = true;
                        }
                    }
                }
                if (cat_has_worn)
                {
                    cat_deletion_list.push_back(obj_id);
                }
                else
                {
                    gInventory.removeCategory(obj_id);
                }
            }
            LLViewerInventoryItem* item = gInventory.getItem(obj_id);
            if (item)
            {
                if (has_worn && get_is_item_worn(item))
                {
                    worn.push_back(item->getUUID());
                    item_deletion_list.push_back(item->getUUID());
                }
                else
                {
                    gInventory.removeItem(obj_id);
                }
            }
        }

        if (!worn.empty())
        {
            // should fire once after every item gets detached
            LLAppearanceMgr::instance().removeItemsFromAvatar(worn,
                                                              [item_deletion_list, cat_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);
                                                                  }
                                                              });
        }
    }
}

void LLInventoryGallery::deleteSelection()
{
    bool has_worn = false;
    bool needs_replacement = false;
    for (const LLUUID& id : mSelectedItemIDs)
    {
        LLViewerInventoryCategory* cat = gInventory.getCategory(id);
        if (cat)
        {
            LLInventoryModel::cat_array_t categories;
            LLInventoryModel::item_array_t items;

            gInventory.collectDescendents(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(id);
        if (item && 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)
    {
        LLNotificationsUtil::add("CantDeleteRequiredClothing");
    }
    else if (has_worn)
    {
        LLSD payload;
        payload["has_worn"] = true;
        LLNotificationsUtil::add("DeleteWornItems", LLSD(), payload, boost::bind(&LLInventoryGallery::onDelete, _1, _2, mSelectedItemIDs));
    }
    else
    {
        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, true))
        {
            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;
    for (LLUUID& selected_id : mSelectedItemIDs)
    {
        const LLInventoryItem* item = gInventory.getItem(selected_id);
        if (item)
        {
            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()))
        {
            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");
    mThumbnailCtrl = getChild<LLThumbnailCtrl>("preview_thumbnail");

    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)
    {
        mThumbnailCtrl->clearTexture();
    }
    else
    {
        mThumbnailCtrl->setValue(id);
    }
}

void LLInventoryGalleryItem::setLoadImmediately(bool val)
{
    mThumbnailCtrl->setInitImmediately(val);
}

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 = mThumbnailCtrl->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);
    mThumbnailCtrl->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 &current_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 &current_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());
}

