/**
 * @file llfolderview.cpp
 * @brief Implementation of the folder view collection of classes.
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llfolderview.h"
#include "llfolderviewmodel.h"
#include "llclipboard.h" // *TODO: remove this once hack below gone.
#include "llkeyboard.h"
#include "lllineeditor.h"
#include "llmenugl.h"
#include "llpanel.h"
#include "llscrollcontainer.h" // hack to allow scrolling
#include "lltextbox.h"
#include "lltrans.h"
#include "llui.h"
#include "lluictrlfactory.h"

// Linden library includes
#include "lldbstrings.h"
#include "llfocusmgr.h"
#include "llfontgl.h"
#include "llgl.h"
#include "llrender.h"

// Third-party library includes
#include <algorithm>

///----------------------------------------------------------------------------
/// Local function declarations, constants, enums, and typedefs
///----------------------------------------------------------------------------

const S32 RENAME_HEIGHT_PAD = 1;
const S32 AUTO_OPEN_STACK_DEPTH = 16;

const S32 MINIMUM_RENAMER_WIDTH = 80;

// *TODO: move in params in xml if necessary. Requires modification of LLFolderView & LLInventoryPanel Params.
const S32 STATUS_TEXT_HPAD = 6;
const S32 STATUS_TEXT_VPAD = 8;

enum {
    SIGNAL_NO_KEYBOARD_FOCUS = 1,
    SIGNAL_KEYBOARD_FOCUS = 2
};

F32 LLFolderView::sAutoOpenTime = 1.f;

//---------------------------------------------------------------------------

// Tells all folders in a folderview to close themselves
// For efficiency, calls setOpenArrangeRecursively().
// The calling function must then call:
//  LLFolderView* root = getRoot();
//  if( root )
//  {
//      root->arrange( NULL, NULL );
//      root->scrollToShowSelection();
//  }
// to patch things up.
class LLCloseAllFoldersFunctor : public LLFolderViewFunctor
{
public:
    LLCloseAllFoldersFunctor(bool close) { mOpen = !close; }
    virtual ~LLCloseAllFoldersFunctor() {}
    virtual void doFolder(LLFolderViewFolder* folder);
    virtual void doItem(LLFolderViewItem* item);

    bool mOpen;
};


void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder)
{
    folder->setOpenArrangeRecursively(mOpen);
}

// Do nothing.
void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item)
{ }

//---------------------------------------------------------------------------

void LLAllDescendentsPassedFilter::doFolder(LLFolderViewFolder* folder)
{
    mAllDescendentsPassedFilter &= (folder) && (folder->passedFilter()) && (folder->descendantsPassedFilter());
}

void LLAllDescendentsPassedFilter::doItem(LLFolderViewItem* item)
{
    mAllDescendentsPassedFilter &= (item) && (item->passedFilter());
}

///----------------------------------------------------------------------------
/// Class LLFolderViewScrollContainer
///----------------------------------------------------------------------------

// virtual
const LLRect LLFolderViewScrollContainer::getScrolledViewRect() const
{
    LLRect rect = LLRect::null;
    if (mScrolledView)
    {
        LLFolderView* folder_view = dynamic_cast<LLFolderView*>(mScrolledView);
        if (folder_view)
        {
            S32 height = folder_view->getRect().getHeight();

            rect = mScrolledView->getRect();
            rect.setLeftTopAndSize(rect.mLeft, rect.mTop, rect.getWidth(), height);
        }
    }

    return rect;
}

LLFolderViewScrollContainer::LLFolderViewScrollContainer(const LLScrollContainer::Params& p)
:   LLScrollContainer(p)
{}

///----------------------------------------------------------------------------
/// Class LLFolderView
///----------------------------------------------------------------------------
LLFolderView::Params::Params()
:   title("title"),
    use_label_suffix("use_label_suffix"),
    allow_multiselect("allow_multiselect", true),
    allow_drag("allow_drag", true),
    show_empty_message("show_empty_message", true),
    suppress_folder_menu("suppress_folder_menu", false),
    use_ellipses("use_ellipses", false),
    options_menu("options_menu", "")
{
    folder_indentation = -4;
}


// Default constructor
LLFolderView::LLFolderView(const Params& p)
:   LLFolderViewFolder(p),
    mScrollContainer( NULL ),
    mPopupMenuHandle(),
    mMenuFileName(p.options_menu),
    mAllowMultiSelect(p.allow_multiselect),
    mAllowDrag(p.allow_drag),
    mShowEmptyMessage(p.show_empty_message),
    mShowFolderHierarchy(false),
    mRenameItem( NULL ),
    mNeedsScroll( false ),
    mUseLabelSuffix(p.use_label_suffix),
    mSuppressFolderMenu(p.suppress_folder_menu),
    mPinningSelectedItem(false),
    mNeedsAutoSelect( false ),
    mAutoSelectOverride(false),
    mNeedsAutoRename(false),
    mShowSelectionContext(false),
    mShowSingleSelection(false),
    mArrangeGeneration(0),
    mSignalSelectCallback(0),
    mMinWidth(0),
    mDragAndDropThisFrame(false),
    mCallbackRegistrar(NULL),
    mEnableRegistrar(NULL),
    mUseEllipses(p.use_ellipses),
    mDraggingOverItem(NULL),
    mStatusTextBox(NULL),
    mShowItemLinkOverlays(p.show_item_link_overlays),
    mViewModel(p.view_model),
    mGroupedItemModel(p.grouped_item_model),
    mForceArrange(false),
    mSingleFolderMode(false)
{
    LLPanel* panel = p.parent_panel;
    mParentPanel = panel->getHandle();
    mViewModel->setFolderView(this);
    mRoot = this;

    LLRect rect = p.rect;
    LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom);
    setRect( rect );
    reshape(rect.getWidth(), rect.getHeight());
    mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH);
    mAutoOpenCandidate = NULL;
    mAutoOpenTimer.stop();
    mKeyboardSelection = false;
    mIndentation =  getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0;

    //clear label
    // go ahead and render root folder as usual
    // just make sure the label ("Inventory Folder") never shows up
    mLabel.clear();

    // Escape is handled by reverting the rename, not commiting it (default behavior)
    LLLineEditor::Params params;
    params.name("ren");
    params.rect(rect);
    params.font(getLabelFontForStyle(LLFontGL::NORMAL));
    params.max_length.bytes(DB_INV_ITEM_NAME_STR_LEN);
    params.commit_callback.function(boost::bind(&LLFolderView::commitRename, this, _2));
    params.prevalidator(&LLTextValidate::validateASCIIPrintableNoPipe);
    params.commit_on_focus_lost(true);
    params.visible(false);
    mRenamer = LLUICtrlFactory::create<LLLineEditor> (params);
    addChild(mRenamer);

    // Textbox
    LLTextBox::Params text_p;
    LLFontGL* font = getLabelFontForStyle(mLabelStyle);
    //mIconPad, mTextPad are set in folder_view_item.xml
    LLRect new_r = LLRect(rect.mLeft + mIconPad,
                  rect.mTop - mTextPad,
                  rect.mRight,
                  rect.mTop - mTextPad - font->getLineHeight());
    text_p.rect(new_r);
    text_p.name(std::string(p.name));
    text_p.font(font);
    text_p.visible(false);
    text_p.parse_urls(true);
    text_p.wrap(true); // allow multiline text. See EXT-7564, EXT-7047
    // set text padding the same as in People panel. EXT-7047, EXT-4837
    text_p.h_pad(STATUS_TEXT_HPAD);
    text_p.v_pad(STATUS_TEXT_VPAD);
    mStatusTextBox = LLUICtrlFactory::create<LLTextBox> (text_p);
    mStatusTextBox->setFollowsLeft();
    mStatusTextBox->setFollowsTop();
    addChild(mStatusTextBox);

    mViewModelItem->openItem();

    mAreChildrenInited = true; // root folder is a special case due to not being loaded normally, assume that it's inited.
}

// Destroys the object
LLFolderView::~LLFolderView( void )
{
    mRenamerTopLostSignalConnection.disconnect();
    if (mRenamer)
    {
        // instead of using closeRenamer remove it directly,
        // since it might already be hidden
        LLUI::getInstance()->removePopup(mRenamer);
    }

    // The release focus call can potentially call the
    // scrollcontainer, which can potentially be called with a partly
    // destroyed scollcontainer. Just null it out here, and no worries
    // about calling into the invalid scroll container.
    // Same with the renamer.
    mScrollContainer = NULL;
    mRenameItem = NULL;
    mRenamer = NULL;
    mStatusTextBox = NULL;

    if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die();
    mPopupMenuHandle.markDead();

    mAutoOpenItems.removeAllNodes();
    clearSelection();
    mItems.clear();
    mFolders.clear();

    //mViewModel->setFolderView(NULL);
    mViewModel = NULL;
}

bool LLFolderView::canFocusChildren() const
{
    return false;
}

void LLFolderView::addFolder( LLFolderViewFolder* folder)
{
    LLFolderViewFolder::addFolder(folder);
}

void LLFolderView::closeAllFolders()
{
    // Close all the folders
    setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN);
    arrangeAll();
}

void LLFolderView::openTopLevelFolders()
{
    for (folders_t::iterator iter = mFolders.begin();
         iter != mFolders.end();)
    {
        folders_t::iterator fit = iter++;
        (*fit)->setOpen(true);
    }
}

// This view grows and shrinks to enclose all of its children items and folders.
// *width should be 0
// conform show folder state works
S32 LLFolderView::arrange( S32* unused_width, S32* unused_height )
    {
    mMinWidth = 0;
    S32 target_height;

    LLFolderViewFolder::arrange(&mMinWidth, &target_height);

    LLRect scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect());
    reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) );

    LLRect new_scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect());
    if (new_scroll_rect.getWidth() != scroll_rect.getWidth())
    {
        reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) );
    }

    // move item renamer text field to item's new position
    updateRenamerPosition();

    return ll_round(mTargetHeight);
}

void LLFolderView::filter( LLFolderViewFilter& filter )
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
    const S32 TIME_VISIBLE = 10; // in milliseconds
    const S32 TIME_INVISIBLE = 1;
    filter.resetTime(llclamp((mParentPanel.get()->getVisible() ? TIME_VISIBLE : TIME_INVISIBLE), 1, 100));

    // Note: we filter the model, not the view
    getViewModelItem()->filter(filter);
}

void LLFolderView::reshape(S32 width, S32 height, bool called_from_parent)
{
    LLRect scroll_rect;
    if (mScrollContainer)
    {
        LLView::reshape(width, height, called_from_parent);
        scroll_rect = mScrollContainer->getContentWindowRect();
    }
    width  = llmax(mMinWidth, scroll_rect.getWidth());
    height = llmax(ll_round(mCurHeight), scroll_rect.getHeight());

    // Restrict width within scroll container's width
    if (mUseEllipses && mScrollContainer)
    {
        width = scroll_rect.getWidth();
    }
    LLView::reshape(width, height, called_from_parent);
    mReshapeSignal(mSelectedItems, false);
}

void LLFolderView::addToSelectionList(LLFolderViewItem* item)
{
    if (item->isSelected())
    {
        removeFromSelectionList(item);
    }
    if (mSelectedItems.size())
    {
        mSelectedItems.back()->setIsCurSelection(false);
    }
    item->setIsCurSelection(true);
    mSelectedItems.push_back(item);
}

void LLFolderView::removeFromSelectionList(LLFolderViewItem* item)
{
    if (mSelectedItems.size())
    {
        mSelectedItems.back()->setIsCurSelection(false);
    }

    selected_items_t::iterator item_iter;
    for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end();)
    {
        if (*item_iter == item)
        {
            item_iter = mSelectedItems.erase(item_iter);
        }
        else
        {
            ++item_iter;
        }
    }
    if (mSelectedItems.size())
    {
        mSelectedItems.back()->setIsCurSelection(true);
    }
}

LLFolderViewItem* LLFolderView::getCurSelectedItem( void )
{
    if(mSelectedItems.size())
    {
        LLFolderViewItem* itemp = mSelectedItems.back();
        llassert(itemp->getIsCurSelection());
        return itemp;
    }
    return NULL;
}

LLFolderView::selected_items_t& LLFolderView::getSelectedItems( void )
{
    return mSelectedItems;
}

// Record the selected item and pass it down the hierachy.
bool LLFolderView::setSelection(LLFolderViewItem* selection, bool openitem,
                                bool take_keyboard_focus)
{
    mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS;

    if( selection == this )
    {
        return false;
    }

    if( selection && take_keyboard_focus)
    {
        mParentPanel.get()->setFocus(true);
    }

    // clear selection down here because change of keyboard focus can potentially
    // affect selection
    clearSelection();

    if(selection)
    {
        addToSelectionList(selection);
    }

    bool rv = LLFolderViewFolder::setSelection(selection, openitem, take_keyboard_focus);
    if(openitem && selection)
    {
        selection->getParentFolder()->requestArrange();
    }

    llassert(mSelectedItems.size() <= 1);

    return rv;
}

bool LLFolderView::changeSelection(LLFolderViewItem* selection, bool selected)
{
    bool rv = false;

    // can't select root folder
    if(!selection || selection == this)
    {
        return false;
    }

    if (!mAllowMultiSelect)
    {
        clearSelection();
    }

    selected_items_t::iterator item_iter;
    for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
    {
        if (*item_iter == selection)
        {
            break;
        }
    }

    bool on_list = (item_iter != mSelectedItems.end());

    if(selected && !on_list)
    {
        addToSelectionList(selection);
    }
    if(!selected && on_list)
    {
        removeFromSelectionList(selection);
    }

    rv = LLFolderViewFolder::changeSelection(selection, selected);

    mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS;

    return rv;
}

void LLFolderView::sanitizeSelection()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
    // store off current item in case it is automatically deselected
    // and we want to preserve context
    LLFolderViewItem* original_selected_item = getCurSelectedItem();

    std::vector<LLFolderViewItem*> items_to_remove;
    selected_items_t::iterator item_iter;
    for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
    {
        LLFolderViewItem* item = *item_iter;

        // ensure that each ancestor is open and potentially passes filtering
        bool visible = false;
        if(item->getViewModelItem() != NULL)
        {
            visible = item->getViewModelItem()->potentiallyVisible(); // initialize from filter state for this item
        }
        // modify with parent open and filters states
        LLFolderViewFolder* parent_folder = item->getParentFolder();
        // Move up through parent folders and see what's visible
                while(parent_folder)
                {
            visible = visible && parent_folder->isOpen() && parent_folder->getViewModelItem()->potentiallyVisible();
                    parent_folder = parent_folder->getParentFolder();
                }

        //  deselect item if any ancestor is closed or didn't pass filter requirements.
        if (!visible)
        {
            items_to_remove.push_back(item);
        }

        // disallow nested selections (i.e. folder items plus one or more ancestors)
        // could check cached mum selections count and only iterate if there are any
        // but that may be a premature optimization.
        selected_items_t::iterator other_item_iter;
        for (other_item_iter = mSelectedItems.begin(); other_item_iter != mSelectedItems.end(); ++other_item_iter)
        {
            LLFolderViewItem* other_item = *other_item_iter;
            for( parent_folder = other_item->getParentFolder(); parent_folder; parent_folder = parent_folder->getParentFolder())
            {
                if (parent_folder == item)
                {
                    // this is a descendent of the current folder, remove from list
                    items_to_remove.push_back(other_item);
                    break;
                }
            }
        }

        // Don't allow invisible items (such as root folders) to be selected.
        if (item == getRoot())
        {
            items_to_remove.push_back(item);
        }
    }

    std::vector<LLFolderViewItem*>::iterator item_it;
    for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it )
    {
        changeSelection(*item_it, false); // toggle selection (also removes from list)
    }

    // if nothing selected after prior constraints...
    if (mSelectedItems.empty())
    {
        // ...select first available parent of original selection
        LLFolderViewItem* new_selection = NULL;
        if (original_selected_item)
        {
            for(LLFolderViewFolder* parent_folder = original_selected_item->getParentFolder();
                parent_folder;
                parent_folder = parent_folder->getParentFolder())
            {
                if (parent_folder->getViewModelItem() && parent_folder->getViewModelItem()->potentiallyVisible())
                {
                    // give initial selection to first ancestor folder that potentially passes the filter
                    if (!new_selection)
                    {
                        new_selection = parent_folder;
                    }

                    // if any ancestor folder of original item is closed, move the selection up
                    // to the highest closed
                    if (!parent_folder->isOpen())
                    {
                        new_selection = parent_folder;
                    }
                }
            }
        }
        else
        {
            new_selection = NULL;
        }

        if (new_selection)
        {
            setSelection(new_selection, false, false);
        }
    }
}

void LLFolderView::clearSelection()
{
    for (selected_items_t::const_iterator item_it = mSelectedItems.begin();
         item_it != mSelectedItems.end();
         ++item_it)
    {
        (*item_it)->setUnselected();
    }

    mSelectedItems.clear();
    mNeedsScroll = false;
}

std::set<LLFolderViewItem*> LLFolderView::getSelectionList() const
{
    std::set<LLFolderViewItem*> selection;
    std::copy(mSelectedItems.begin(), mSelectedItems.end(), std::inserter(selection, selection.begin()));
    return selection;
}

bool LLFolderView::startDrag()
{
    std::vector<LLFolderViewModelItem*> selected_items;
    selected_items_t::iterator item_it;

    if (!mSelectedItems.empty())
    {
        for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
        {
            selected_items.push_back((*item_it)->getViewModelItem());
        }

        return getFolderViewModel()->startDrag(selected_items);
    }
    return false;
}

void LLFolderView::commitRename( const LLSD& data )
{
    finishRenamingItem();
    arrange( NULL, NULL );

}

void LLFolderView::draw()
{
    //LLFontGL* font = getLabelFontForStyle(mLabelStyle);

    // if cursor has moved off of me during drag and drop
    // close all auto opened folders
    if (!mDragAndDropThisFrame)
    {
        closeAutoOpenedFolders();
    }

    static LLCachedControl<F32> type_ahead_timeout(*LLUI::getInstance()->mSettingGroups["config"], "TypeAheadTimeout", 1.5f);
    if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout || !mSearchString.size())
    {
        mSearchString.clear();
    }

    if (hasVisibleChildren())
    {
        mStatusTextBox->setVisible( false );
    }
    else if (mShowEmptyMessage)
    {
        mStatusTextBox->setValue(getFolderViewModel()->getStatusText(mItems.empty() && mFolders.empty()));
        mStatusTextBox->setVisible( true );

        // firstly reshape message textbox with current size. This is necessary to
        // LLTextBox::getTextPixelHeight works properly
        const LLRect local_rect = getLocalRect();
        mStatusTextBox->setShape(local_rect);

        // get preferable text height...
        S32 pixel_height = mStatusTextBox->getTextPixelHeight();
        bool height_changed = (local_rect.getHeight() < pixel_height);
        if (height_changed)
        {
            // ... if it does not match current height, lets rearrange current view.
            // This will indirectly call ::arrange and reshape of the status textbox.
            // We should call this method to also notify parent about required rect.
            // See EXT-7564, EXT-7047.
            S32 height = 0;
            S32 width = 0;
            S32 total_height = arrange( &width, &height );
            notifyParent(LLSD().with("action", "size_changes").with("height", total_height));

            LLUI::popMatrix();
            LLUI::pushMatrix();
            LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom);
        }
    }

    if (mRenameItem
        && mRenamer
        && mRenamer->getVisible()
        && !getVisibleRect().overlaps(mRenamer->getRect()))
    {
        // renamer is not connected to the item we are renaming in any form so manage it manually
        // TODO: consider stopping on any scroll action instead of when out of visible area
        LL_DEBUGS("Inventory") << "Renamer out of bounds, hiding" << LL_ENDL;
        finishRenamingItem();
    }

    // skip over LLFolderViewFolder::draw since we don't want the folder icon, label,
    // and arrow for the root folder
    LLView::draw();

    mDragAndDropThisFrame = false;
}

void LLFolderView::finishRenamingItem( void )
{
    if(!mRenamer)
    {
        return;
    }
    if( mRenameItem )
    {
        mRenameItem->rename( mRenamer->getText() );
    }

    closeRenamer();

    // This is moved to an inventory observer in llinventorybridge.cpp, to handle updating after operation completed in AISv3 (SH-4611).
    // List is re-sorted alphabetically, so scroll to make sure the selected item is visible.
    //scrollToShowSelection();
}

void LLFolderView::closeRenamer( void )
{
    if (mRenamer && mRenamer->getVisible())
    {
        // Triggers onRenamerLost() that actually closes the renamer.
        LLUI::getInstance()->removePopup(mRenamer);
    }
}

void LLFolderView::removeSelectedItems()
{
    if(getVisible() && getEnabled())
    {
        // just in case we're removing the renaming item.
        mRenameItem = NULL;

        // create a temporary structure which we will use to remove
        // items, since the removal will futz with internal data
        // structures.
        std::vector<LLFolderViewItem*> items;
        auto count = mSelectedItems.size();
        if(count <= 0) return;
        LLFolderViewItem* item = NULL;
        selected_items_t::iterator item_it;
        for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
        {
            item = *item_it;
            if (item && item->isRemovable())
            {
                items.push_back(item);
            }
            else
            {
                LL_DEBUGS() << "Cannot delete " << item->getName() << LL_ENDL;
                return;
            }
        }

        // iterate through the new container.
        count = items.size();
        LLUUID new_selection_id;
        LLFolderViewItem* item_to_select = getNextUnselectedItem();

        if(count == 1)
        {
            LLFolderViewItem* item_to_delete = items[0];
            LLFolderViewFolder* parent = item_to_delete->getParentFolder();
            if(parent)
            {
                if (item_to_delete->remove())
                {
                    // change selection on successful delete
                    setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus());
                }
            }
            arrangeAll();
        }
        else if (count > 1)
        {
            std::vector<LLFolderViewModelItem*> listeners;
            LLFolderViewModelItem* listener;

            setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus());

            listeners.reserve(count);
            for(size_t i = 0; i < count; ++i)
            {
                listener = items[i]->getViewModelItem();
                if(listener && (std::find(listeners.begin(), listeners.end(), listener) == listeners.end()))
                {
                    listeners.push_back(listener);
                }
            }
            listener = static_cast<LLFolderViewModelItem*>(listeners.at(0));
            if(listener)
            {
                listener->removeBatch(listeners);
            }
        }
        arrangeAll();
        scrollToShowSelection();
    }
}

void LLFolderView::autoOpenItem( LLFolderViewFolder* item )
{
    if ((mAutoOpenItems.check() == item) ||
        (mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH) ||
        item->isOpen())
    {
        return;
    }

    // close auto-opened folders
    LLFolderViewFolder* close_item = mAutoOpenItems.check();
    while (close_item && close_item != item->getParentFolder())
    {
        mAutoOpenItems.pop();
        close_item->setOpenArrangeRecursively(false);
        close_item = mAutoOpenItems.check();
    }

    item->requestArrange();

    mAutoOpenItems.push(item);

    item->setOpen(true);
    if(!item->isSingleFolderMode())
    {
    LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect());
    LLRect constraint_rect(0,content_rect.getHeight(), content_rect.getWidth(), 0);
    scrollToShowItem(item, constraint_rect);
    }
}

void LLFolderView::closeAutoOpenedFolders()
{
    while (mAutoOpenItems.check())
    {
        LLFolderViewFolder* close_item = mAutoOpenItems.pop();
        close_item->setOpen(false);
    }

    if (mAutoOpenCandidate)
    {
        mAutoOpenCandidate->setAutoOpenCountdown(0.f);
    }
    mAutoOpenCandidate = NULL;
    mAutoOpenTimer.stop();
}

bool LLFolderView::autoOpenTest(LLFolderViewFolder* folder)
{
    if (folder && mAutoOpenCandidate == folder)
    {
        if (mAutoOpenTimer.getStarted())
        {
            if (!mAutoOpenCandidate->isOpen())
            {
                mAutoOpenCandidate->setAutoOpenCountdown(clamp_rescale(mAutoOpenTimer.getElapsedTimeF32(), 0.f, sAutoOpenTime, 0.f, 1.f));
            }
            if (mAutoOpenTimer.getElapsedTimeF32() > sAutoOpenTime)
            {
                autoOpenItem(folder);
                mAutoOpenTimer.stop();
                return true;
            }
        }
        return false;
    }

    // otherwise new candidate, restart timer
    if (mAutoOpenCandidate)
    {
        mAutoOpenCandidate->setAutoOpenCountdown(0.f);
    }
    mAutoOpenCandidate = folder;
    mAutoOpenTimer.start();
    return false;
}

bool LLFolderView::canCopy() const
{
    if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0)))
    {
        return false;
    }

    for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
    {
        const LLFolderViewItem* item = *selected_it;
        if (!item->getViewModelItem()->isItemCopyable())
        {
            return false;
        }
    }
    return true;
}

// copy selected item
void LLFolderView::copy()
{
    // *NOTE: total hack to clear the inventory clipboard
    LLClipboard::instance().reset();
    auto count = mSelectedItems.size();
    if(getVisible() && getEnabled() && (count > 0))
    {
        LLFolderViewModelItem* listener = NULL;
        selected_items_t::iterator item_it;
        for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
        {
            listener = (*item_it)->getViewModelItem();
            if(listener)
            {
                listener->copyToClipboard();
            }
        }
    }
    mSearchString.clear();
}

bool LLFolderView::canCut() const
{
    if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0)))
    {
        return false;
    }

    for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
    {
        const LLFolderViewItem* item = *selected_it;
        const LLFolderViewModelItem* listener = item->getViewModelItem();

        if (!listener || !listener->isItemRemovable())
        {
            return false;
        }
    }
    return true;
}

void LLFolderView::cut()
{
    // clear the inventory clipboard
    LLClipboard::instance().reset();
    if(getVisible() && getEnabled() && (mSelectedItems.size() > 0))
    {
        // Find out which item will be selected once the selection will be cut
        LLFolderViewItem* item_to_select = getNextUnselectedItem();

        // Get the selection: removeItem() modified mSelectedItems and makes iterating on it unwise
        std::set<LLFolderViewItem*> inventory_selected = getSelectionList();

        // Move each item to the clipboard and out of their folder
        for (std::set<LLFolderViewItem*>::iterator item_it = inventory_selected.begin(); item_it != inventory_selected.end(); ++item_it)
        {
            LLFolderViewItem* item_to_cut = *item_it;
            LLFolderViewModelItem* listener = item_to_cut->getViewModelItem();
            if (listener)
            {
                listener->cutToClipboard();
            }
        }

        // Update the selection
        setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus());
    }
    mSearchString.clear();
}

bool LLFolderView::canPaste() const
{
    if (mSelectedItems.empty())
    {
        return false;
    }

    if(getVisible() && getEnabled())
    {
        for (selected_items_t::const_iterator item_it = mSelectedItems.begin();
             item_it != mSelectedItems.end(); ++item_it)
        {
            // *TODO: only check folders and parent folders of items
            const LLFolderViewItem* item = (*item_it);
            const LLFolderViewModelItem* listener = item->getViewModelItem();
            if(!listener || !listener->isClipboardPasteable())
            {
                const LLFolderViewFolder* folderp = item->getParentFolder();
                listener = folderp->getViewModelItem();
                if (!listener || !listener->isClipboardPasteable())
                {
                    return false;
                }
            }
        }
        return true;
    }
    return false;
}

// paste selected item
void LLFolderView::paste()
{
    if(getVisible() && getEnabled())
    {
        // find set of unique folders to paste into
        std::set<LLFolderViewFolder*> folder_set;

        selected_items_t::iterator selected_it;
        for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it)
        {
            LLFolderViewItem* item = *selected_it;
            LLFolderViewFolder* folder = dynamic_cast<LLFolderViewFolder*>(item);
            if (folder == NULL)
            {
                folder = item->getParentFolder();
            }
            folder_set.insert(folder);
        }

        std::set<LLFolderViewFolder*>::iterator set_iter;
        for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter)
        {
            LLFolderViewModelItem* listener = (*set_iter)->getViewModelItem();
            if(listener && listener->isClipboardPasteable())
            {
                listener->pasteFromClipboard();
            }
        }
    }
    mSearchString.clear();
}

// public rename functionality - can only start the process
void LLFolderView::startRenamingSelectedItem( void )
{
    LL_DEBUGS("Inventory") << "Starting inventory renamer" << LL_ENDL;

    // make sure selection is visible
    scrollToShowSelection();

    auto count = mSelectedItems.size();
    LLFolderViewItem* item = NULL;
    if(count > 0)
    {
        item = mSelectedItems.front();
    }
    if(getVisible() && getEnabled() && (count == 1) && item && item->getViewModelItem() &&
       item->getViewModelItem()->isItemRenameable())
    {
        mRenameItem = item;

        updateRenamerPosition();


        mRenamer->setText(item->getName());
        mRenamer->selectAll();
        mRenamer->setVisible( true );
        // set focus will fail unless item is visible
        mRenamer->setFocus( true );
        if (!mRenamerTopLostSignalConnection.connected())
        {
            mRenamerTopLostSignalConnection = mRenamer->setTopLostCallback(boost::bind(&LLFolderView::onRenamerLost, this));
        }
        LLUI::getInstance()->addPopup(mRenamer);
    }
}

bool LLFolderView::handleKeyHere( KEY key, MASK mask )
{
    bool handled = false;

    // SL-51858: Key presses are not being passed to the Popup menu.
    // A proper fix is non-trivial so instead just close the menu.
    LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
    if (menu && menu->isOpen())
    {
        LLMenuGL::sMenuContainer->hideMenus();
    }

    switch( key )
    {
    case KEY_F2:
        mSearchString.clear();
        startRenamingSelectedItem();
        handled = true;
        break;

    case KEY_RETURN:
        if (mask == MASK_NONE)
        {
            if( mRenameItem && mRenamer->getVisible() )
            {
                finishRenamingItem();
                mSearchString.clear();
                handled = true;
            }
        }
        break;

    case KEY_ESCAPE:
        if( mRenameItem && mRenamer->getVisible() )
        {
            closeRenamer();
            handled = true;
        }
        mSearchString.clear();
        break;

    case KEY_PAGE_UP:
        mSearchString.clear();
        if (mScrollContainer)
        {
        mScrollContainer->pageUp(30);
        }
        handled = true;
        break;

    case KEY_PAGE_DOWN:
        mSearchString.clear();
        if (mScrollContainer)
        {
        mScrollContainer->pageDown(30);
        }
        handled = true;
        break;

    case KEY_HOME:
        mSearchString.clear();
        if (mScrollContainer)
        {
        mScrollContainer->goToTop();
        }
        handled = true;
        break;

    case KEY_END:
        mSearchString.clear();
        if (mScrollContainer)
        {
        mScrollContainer->goToBottom();
        }
        break;

    case KEY_DOWN:
        if((mSelectedItems.size() > 0) && mScrollContainer)
        {
            LLFolderViewItem* last_selected = getCurSelectedItem();
            bool shift_select = mask & MASK_SHIFT;
            // don't shift select down to children of folders (they are implicitly selected through parent)
            LLFolderViewItem* next = last_selected->getNextOpenNode(!shift_select);

            if (!mKeyboardSelection || (!shift_select && (!next || next == last_selected)))
            {
                setSelection(last_selected, false, true);
                mKeyboardSelection = true;
            }

            if (shift_select)
            {
                if (next)
                {
                    if (next->isSelected())
                    {
                        // shrink selection
                        changeSelection(last_selected, false);
                    }
                    else if (last_selected->getParentFolder() == next->getParentFolder())
                    {
                        // grow selection
                        changeSelection(next, true);
                    }
                }
            }
            else
            {
                if( next )
                {
                    if (next == last_selected)
                    {
                        //special case for LLAccordionCtrl
                        if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed
                        {
                            clearSelection();
                            return true;
                        }
                        return false;
                    }
                    setSelection( next, false, true );
                }
                else
                {
                    //special case for LLAccordionCtrl
                    if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed
                    {
                        clearSelection();
                        return true;
                    }
                    return false;
                }
            }
            scrollToShowSelection();
            mSearchString.clear();
            handled = true;
        }
        break;

    case KEY_UP:
        if((mSelectedItems.size() > 0) && mScrollContainer)
        {
            LLFolderViewItem* last_selected = mSelectedItems.back();
            bool shift_select = mask & MASK_SHIFT;
            // don't shift select down to children of folders (they are implicitly selected through parent)
            LLFolderViewItem* prev = last_selected->getPreviousOpenNode(!shift_select);

            if (!mKeyboardSelection || (!shift_select && prev == this))
            {
                setSelection(last_selected, false, true);
                mKeyboardSelection = true;
            }

            if (shift_select)
            {
                if (prev)
                {
                    if (prev->isSelected())
                    {
                        // shrink selection
                        changeSelection(last_selected, false);
                    }
                    else if (last_selected->getParentFolder() == prev->getParentFolder())
                    {
                        // grow selection
                        changeSelection(prev, true);
                    }
                }
            }
            else
            {
                if( prev )
                {
                    if (prev == this)
                    {
                        // If case we are in accordion tab notify parent to go to the previous accordion
                        if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed
                        {
                            clearSelection();
                            return true;
                        }

                        return false;
                    }
                    setSelection( prev, false, true );
                }
            }
            scrollToShowSelection();
            mSearchString.clear();

            handled = true;
        }
        break;

    case KEY_RIGHT:
        if(mSelectedItems.size())
        {
            LLFolderViewItem* last_selected = getCurSelectedItem();
            last_selected->setOpen( true );
            mSearchString.clear();
            handled = true;
        }
        break;

    case KEY_LEFT:
        if(mSelectedItems.size())
        {
            LLFolderViewItem* last_selected = getCurSelectedItem();
            if(last_selected && last_selected->isSingleFolderMode())
            {
                handled = false;
                break;
            }
            LLFolderViewItem* parent_folder = last_selected->getParentFolder();
            if (!last_selected->isOpen() && parent_folder && parent_folder->getParentFolder())
            {
                setSelection(parent_folder, false, true);
            }
            else
            {
                last_selected->setOpen( false );
            }
            mSearchString.clear();
            scrollToShowSelection();
            handled = true;
        }
        break;
    }

    return handled;
}


bool LLFolderView::handleUnicodeCharHere(llwchar uni_char)
{
    if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
    {
        return false;
    }

    if (uni_char > 0x7f)
    {
        LL_WARNS() << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << LL_ENDL;
        return false;
    }

    bool handled = false;
    if (mParentPanel.get()->hasFocus())
    {
        // SL-51858: Key presses are not being passed to the Popup menu.
        // A proper fix is non-trivial so instead just close the menu.
        LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
        if (menu && menu->isOpen())
        {
            LLMenuGL::sMenuContainer->hideMenus();
        }

        //do text search
        if (mSearchTimer.getElapsedTimeF32() > LLUI::getInstance()->mSettingGroups["config"]->getF32("TypeAheadTimeout"))
        {
            mSearchString.clear();
        }
        mSearchTimer.reset();
        if (mSearchString.size() < 128)
        {
            mSearchString += uni_char;
        }
        search(getCurSelectedItem(), mSearchString, false);

        handled = true;
    }

    return handled;
}


bool LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask )
{
    mKeyboardSelection = false;
    mSearchString.clear();

    mParentPanel.get()->setFocus(true);

    LLEditMenuHandler::gEditMenuHandler = this;

    return LLView::handleMouseDown( x, y, mask );
}

bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &search_string, bool backward)
{
    // get first selected item
    LLFolderViewItem* search_item = first_item;

    // make sure search string is upper case
    std::string upper_case_string = search_string;
    LLStringUtil::toUpper(upper_case_string);

    // if nothing selected, select first item in folder
    if (!search_item)
    {
        // start from first item
        search_item = getNextFromChild(NULL);
    }

    // search over all open nodes for first substring match (with wrapping)
    bool found = false;
    LLFolderViewItem* original_search_item = search_item;
    do
    {
        // wrap at end
        if (!search_item)
        {
            if (backward)
            {
                search_item = getPreviousFromChild(NULL);
            }
            else
            {
                search_item = getNextFromChild(NULL);
            }
            if (!search_item || search_item == original_search_item)
            {
                break;
            }
        }

        std::string current_item_label(search_item->getViewModelItem()->getSearchableName());
        LLStringUtil::toUpper(current_item_label);
        auto search_string_length = llmin(upper_case_string.size(), current_item_label.size());
        if (!current_item_label.compare(0, search_string_length, upper_case_string))
        {
            found = true;
            break;
        }
        if (backward)
        {
            search_item = search_item->getPreviousOpenNode();
        }
        else
        {
            search_item = search_item->getNextOpenNode();
        }

    } while(search_item != original_search_item);


    if (found)
    {
        setSelection(search_item, false, true);
        scrollToShowSelection();
    }

    return found;
}

bool LLFolderView::handleDoubleClick( S32 x, S32 y, MASK mask )
{
    // skip LLFolderViewFolder::handleDoubleClick()
    return LLView::handleDoubleClick( x, y, mask );
}

bool LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
    // all user operations move keyboard focus to inventory
    // this way, we know when to stop auto-updating a search
    mParentPanel.get()->setFocus(true);

    bool handled = childrenHandleRightMouseDown(x, y, mask) != NULL;
    auto count = mSelectedItems.size();

    LLMenuGL* menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
    if (!menu)
    {
        if (mCallbackRegistrar)
        {
            mCallbackRegistrar->pushScope();
        }
        if (mEnableRegistrar)
        {
            mEnableRegistrar->pushScope();
        }
        llassert(LLMenuGL::sMenuContainer != NULL);
        menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(mMenuFileName, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
        if (!menu)
        {
            menu = LLUICtrlFactory::getDefaultWidget<LLMenuGL>("inventory_menu");
        }
        menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor"));
        mPopupMenuHandle = menu->getHandle();
        if (mEnableRegistrar)
        {
            mEnableRegistrar->popScope();
        }
        if (mCallbackRegistrar)
        {
            mCallbackRegistrar->popScope();
        }
    }

    bool item_clicked{ false };
    for (const auto item : mSelectedItems)
    {
        item_clicked |= item->getRect().pointInRect(x, y);
    }
    if(!item_clicked && mSingleFolderMode)
    {
        clearSelection();
    }
    bool hide_folder_menu = mSuppressFolderMenu && isFolderSelected();
    if (menu && (mSingleFolderMode || (handled
        && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible
        !hide_folder_menu)
    {
        if (mCallbackRegistrar)
        {
            mCallbackRegistrar->pushScope();
        }
        if (mEnableRegistrar)
        {
            mEnableRegistrar->pushScope();
        }

        updateMenuOptions(menu);

        menu->updateParent(LLMenuGL::sMenuContainer);
        LLMenuGL::showPopup(this, menu, x, y);
        if (mEnableRegistrar)
        {
            mEnableRegistrar->popScope();
        }
        if (mCallbackRegistrar)
        {
            mCallbackRegistrar->popScope();
        }
    }
    else
    {
        if (menu && menu->getVisible())
        {
            menu->setVisible(false);
        }
        setSelection(NULL, false, true);
    }
    return handled;
}

// Add "--no options--" if the menu is completely blank.
bool LLFolderView::addNoOptions(LLMenuGL* menu) const
{
    const std::string nooptions_str = "--no options--";
    LLView *nooptions_item = NULL;

    const LLView::child_list_t *list = menu->getChildList();
    for (LLView::child_list_t::const_iterator itor = list->begin();
         itor != list->end();
         ++itor)
    {
        LLView *menu_item = (*itor);
        if (menu_item->getVisible())
        {
            return false;
        }
        std::string name = menu_item->getName();
        if (menu_item->getName() == nooptions_str)
        {
            nooptions_item = menu_item;
        }
    }
    if (nooptions_item)
    {
        nooptions_item->setVisible(true);
        nooptions_item->setEnabled(false);
        return true;
    }
    return false;
}

bool LLFolderView::handleHover( S32 x, S32 y, MASK mask )
{
    return LLView::handleHover( x, y, mask );
}

LLFolderViewItem* LLFolderView::getHoveredItem() const
{
    return dynamic_cast<LLFolderViewItem*>(mHoveredItem.get());
}

void LLFolderView::setHoveredItem(LLFolderViewItem* itemp)
{
    if (mHoveredItem.get() != itemp)
    {
        if (itemp)
            mHoveredItem = itemp->getHandle();
        else
            mHoveredItem.markDead();
    }
}

bool LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop,
                                     EDragAndDropType cargo_type,
                                     void* cargo_data,
                                     EAcceptance* accept,
                                     std::string& tooltip_msg)
{
    mDragAndDropThisFrame = true;
    // 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 folder which is the hierarchy root.
    if (!handled)
    {
            handled = LLFolderViewFolder::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
        }

    return handled;
}

void LLFolderView::deleteAllChildren()
{
    mRenamerTopLostSignalConnection.disconnect();
    if (mRenamer)
    {
        LLUI::getInstance()->removePopup(mRenamer);
    }
    if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die();
    mPopupMenuHandle.markDead();
    mScrollContainer = NULL;
    mRenameItem = NULL;
    mRenamer = NULL;
    mStatusTextBox = NULL;

    clearSelection();
    LLView::deleteAllChildren();
}

void LLFolderView::scrollToShowSelection()
{
    if ( mSelectedItems.size() )
    {
        mNeedsScroll = true;
    }
}

// If the parent is scroll container, scroll it to make the selection
// is maximally visible.
void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect)
{
    if (!mScrollContainer) return;

    // don't scroll to items when mouse is being used to scroll/drag and drop
    if (gFocusMgr.childHasMouseCapture(mScrollContainer))
    {
        mNeedsScroll = false;
        return;
    }

    // if item exists and is in visible portion of parent folder...
    if(item)
    {
        LLRect local_rect = item->getLocalRect();
        S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight();
        S32 label_height = getLabelFontForStyle(mLabelStyle)->getLineHeight();
        // when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder
        S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + item->getIconPad()) : local_rect.getHeight();

        // get portion of item that we want to see...
        LLRect item_local_rect = LLRect(item->getIndentation(),
                                        local_rect.getHeight(),
                                        //+40 is supposed to include few first characters
                                        llmin(item->getLabelXPos() - item->getIndentation() + 40, local_rect.getWidth()),
                                        llmax(0, local_rect.getHeight() - max_height_to_show));

        LLRect item_doc_rect;

        item->localRectToOtherView(item_local_rect, &item_doc_rect, this);

        mScrollContainer->scrollToShowRect( item_doc_rect, constraint_rect );

    }
}

LLRect LLFolderView::getVisibleRect()
{
    S32 visible_height = (mScrollContainer ? mScrollContainer->getRect().getHeight() : 0);
    S32 visible_width  = (mScrollContainer ? mScrollContainer->getRect().getWidth()  : 0);
    LLRect visible_rect;
    visible_rect.setLeftTopAndSize(-getRect().mLeft, visible_height - getRect().mBottom, visible_width, visible_height);
    return visible_rect;
}

bool LLFolderView::getShowSelectionContext()
{
    if (mShowSelectionContext)
    {
        return true;
    }
    LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
    if (menu && menu->getVisible())
    {
        return true;
    }
    return false;
}

void LLFolderView::setShowSingleSelection(bool show)
{
    if (show != mShowSingleSelection)
    {
        mMultiSelectionFadeTimer.reset();
        mShowSingleSelection = show;
    }
}

static LLTrace::BlockTimerStatHandle FTM_INVENTORY("Inventory");

// Main idle routine
void LLFolderView::update()
{
    // If this is associated with the user's inventory, don't do anything
    // until that inventory is loaded up.
    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_INVENTORY);

    // If there's no model, the view is in suspended state (being deleted) and shouldn't be updated
    if (getFolderViewModel() == NULL)
    {
        return;
    }

    LLFolderViewFilter& filter_object = getFolderViewModel()->getFilter();

    if (filter_object.isModified() && filter_object.isNotDefault() && mParentPanel.get()->getVisible())
    {
        mNeedsAutoSelect = true;
    }

    // Filter to determine visibility before arranging
    filter(filter_object);

    // Clear the modified setting on the filter only if the filter finished after running the filter process
    // Note: if the filter count has timed out, that means the filter halted before completing the entire set of items
    bool filter_modified = filter_object.isModified();
    if (filter_modified && (!filter_object.isTimedOut()))
    {
        filter_object.clearModified();
    }

    // automatically show matching items, and select first one if we had a selection
    if (mNeedsAutoSelect)
    {
        // select new item only if a filtered item not currently selected and there was a selection
        LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back();
        if (!mAutoSelectOverride && selected_itemp && !selected_itemp->getViewModelItem()->potentiallyVisible())
        {
            // these are named variables to get around gcc not binding non-const references to rvalues
            // and functor application is inherently non-const to allow for stateful functors
            LLSelectFirstFilteredItem functor;
            applyFunctorRecursively(functor);
        }

        // Open filtered folders for folder views with mAutoSelectOverride=true.
        // Used by LLPlacesFolderView.
        if (filter_object.showAllResults())
        {
            // these are named variables to get around gcc not binding non-const references to rvalues
            // and functor application is inherently non-const to allow for stateful functors
            LLOpenFilteredFolders functor;
            applyFunctorRecursively(functor);
        }

        scrollToShowSelection();
    }

    bool filter_finished = mViewModel->contentsReady()
                            && (getViewModelItem()->passedFilter()
                                || ( getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration()
                                    && !filter_modified));
    if (filter_finished
        || gFocusMgr.childHasKeyboardFocus(mParentPanel.get())
        || gFocusMgr.childHasMouseCapture(mParentPanel.get()))
    {
        // finishing the filter process, giving focus to the folder view, or dragging the scrollbar all stop the auto select process
        mNeedsAutoSelect = false;
    }

  bool is_visible = isInVisibleChain() || mForceArrange;

  //Puts folders/items in proper positions
  // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849)
  // It also handles the open/close folder animation
  if ( is_visible )
  {
    sanitizeSelection();
    if( needsArrange() )
    {
      S32 height = 0;
      S32 width = 0;
      S32 total_height = arrange( &width, &height );
      notifyParent(LLSD().with("action", "size_changes").with("height", total_height));
    }
  }

    // during filtering process, try to pin selected item's location on screen
    // this will happen when searching your inventory and when new items arrive
    if (!filter_finished)
    {
        // calculate rectangle to pin item to at start of animated rearrange
        if (!mPinningSelectedItem && !mSelectedItems.empty())
        {
            // lets pin it!
            mPinningSelectedItem = true;

      //Computes visible area
            const LLRect visible_content_rect = (mScrollContainer ? mScrollContainer->getVisibleContentRect() : LLRect());
            LLFolderViewItem* selected_item = mSelectedItems.back();

      //Computes location of selected content, content outside visible area will be scrolled to using below code
            LLRect item_rect;
            selected_item->localRectToOtherView(selected_item->getLocalRect(), &item_rect, this);

      //Computes intersected region of the selected content and visible area
      LLRect overlap_rect(item_rect);
      overlap_rect.intersectWith(visible_content_rect);

      //Don't scroll when the selected content exists within the visible area
            if (overlap_rect.getHeight() >= selected_item->getItemHeight())
            {
                // then attempt to keep it in same place on screen
                mScrollConstraintRect = item_rect;
                mScrollConstraintRect.translate(-visible_content_rect.mLeft, -visible_content_rect.mBottom);
            }
      //Scroll because the selected content is outside the visible area
            else
            {
                // otherwise we just want it onscreen somewhere
                LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect());
                mScrollConstraintRect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight());
            }
        }
    }
    else
    {
        // stop pinning selected item after folders stop rearranging
        if (!needsArrange())
        {
            mPinningSelectedItem = false;
        }
    }

    LLRect constraint_rect;
    if (mPinningSelectedItem)
    {
        // use last known constraint rect for pinned item
        constraint_rect = mScrollConstraintRect;
    }
    else
    {
        // during normal use (page up/page down, etc), just try to fit item on screen
        LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect());
        constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight());
    }

    if (mSelectedItems.size() && mNeedsScroll)
    {
        LLFolderViewItem* scroll_to_item = mSelectedItems.back();
        scrollToShowItem(scroll_to_item, constraint_rect);
        // continue scrolling until animated layout change is done
        bool selected_filter_finished = getRoot()->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration();
        if (selected_filter_finished && scroll_to_item && scroll_to_item->getViewModelItem())
        {
            selected_filter_finished = scroll_to_item->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration();
        }
        if (filter_finished && selected_filter_finished)
        {
            bool needs_arrange = needsArrange() || getRoot()->needsArrange();
            if (mParentFolder)
            {
                needs_arrange |= (bool)mParentFolder->needsArrange();
            }
            if (!needs_arrange || !is_visible)
            {
                mNeedsScroll = false;
            }
        }
    }

    if (mSelectedItems.size())
    {
        LLFolderViewItem* item = mSelectedItems.back();
        // If the goal is to show renamer, don't callback untill
        // item is visible or is no longer being scrolled to.
        // Otherwise renamer will be instantly closed
        // Todo: consider moving renamer out of selection callback
        if (!mNeedsAutoRename || !mNeedsScroll || item->getVisible())
        {
            if (mSignalSelectCallback)
            {
                //RN: we use keyboard focus as a proxy for user-explicit actions
                bool take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS);
                mSelectSignal(mSelectedItems, take_keyboard_focus);
            }
            mSignalSelectCallback = false;
        }
    }
    else
    {
        mSignalSelectCallback = false;
    }
}

void LLFolderView::dumpSelectionInformation()
{
    LL_INFOS() << "LLFolderView::dumpSelectionInformation()" << LL_NEWLINE
                << "****************************************" << LL_ENDL;
    selected_items_t::iterator item_it;
    for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it)
    {
        LL_INFOS() << "  " << (*item_it)->getName() << LL_ENDL;
    }
    LL_INFOS() << "****************************************" << LL_ENDL;
}

void LLFolderView::updateRenamerPosition()
{
    if(mRenameItem)
    {
        // See also LLFolderViewItem::draw()
        S32 x = mRenameItem->getLabelXPos();
        S32 y = mRenameItem->getRect().getHeight() - mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD;
        mRenameItem->localPointToScreen( x, y, &x, &y );
        screenPointToLocal( x, y, &x, &y );
        mRenamer->setOrigin( x, y );

        LLRect scroller_rect(0, 0, (S32)LLUI::getInstance()->getWindowSize().mV[VX], 0);
        if (mScrollContainer)
        {
            scroller_rect = mScrollContainer->getContentWindowRect();
        }

        S32 width = llmax(llmin(mRenameItem->getRect().getWidth() - x, scroller_rect.getWidth() - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH);
        S32 height = mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD;
        mRenamer->reshape( width, height, true );
    }
}

// Update visibility and availability (i.e. enabled/disabled) of context menu items.
void LLFolderView::updateMenuOptions(LLMenuGL* menu)
{
    const LLView::child_list_t *list = menu->getChildList();

    LLView::child_list_t::const_iterator menu_itor;
    for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor)
    {
        (*menu_itor)->setVisible(false);
        (*menu_itor)->pushVisible(true);
        (*menu_itor)->setEnabled(true);
    }

    // Successively filter out invalid options
    U32 multi_select_flag = (mSelectedItems.size() > 1 ? ITEM_IN_MULTI_SELECTION : 0x0);
    U32 flags = multi_select_flag | FIRST_SELECTED_ITEM;
    for (selected_items_t::iterator item_itor = mSelectedItems.begin();
            item_itor != mSelectedItems.end();
            ++item_itor)
    {
        LLFolderViewItem* selected_item = (*item_itor);
        selected_item->buildContextMenu(*menu, flags);
        flags = multi_select_flag;
    }

    if(mSingleFolderMode && (mSelectedItems.size() == 0))
    {
        buildContextMenu(*menu, flags);
    }

    // This adds a check for restrictions based on the entire
    // selection set - for example, any one wearable may not push you
    // over the limit, but all wearables together still might.
    if (getFolderViewGroupedItemModel())
    {
        getFolderViewGroupedItemModel()->groupFilterContextMenu(mSelectedItems,*menu);
    }

    addNoOptions(menu);
}

// Refresh the context menu (that is already shown).
void LLFolderView::updateMenu()
{
    LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
    if (menu && menu->getVisible())
    {
        updateMenuOptions(menu);
        menu->needsArrange(); // update menu height if needed
    }
}

bool LLFolderView::isFolderSelected()
{
    selected_items_t::iterator item_iter;
    for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter)
    {
        LLFolderViewFolder* folder = dynamic_cast<LLFolderViewFolder*>(*item_iter);
        if (folder != NULL)
        {
            return true;
        }
    }
    return false;
}

bool LLFolderView::selectFirstItem()
{
    for (folders_t::iterator iter = mFolders.begin();
         iter != mFolders.end();++iter)
    {
        LLFolderViewFolder* folder = (*iter );
        if (folder->getVisible())
        {
            LLFolderViewItem* itemp = folder->getNextFromChild(0,true);
            if(itemp)
                setSelection(itemp,false,true);
            return true;
        }

    }
    for(items_t::iterator iit = mItems.begin();
        iit != mItems.end(); ++iit)
    {
        LLFolderViewItem* itemp = (*iit);
        if (itemp->getVisible())
        {
            setSelection(itemp,false,true);
            return true;
        }
    }
    return false;
}
bool LLFolderView::selectLastItem()
{
    for(items_t::reverse_iterator iit = mItems.rbegin();
        iit != mItems.rend(); ++iit)
    {
        LLFolderViewItem* itemp = (*iit);
        if (itemp->getVisible())
        {
            setSelection(itemp,false,true);
            return true;
        }
    }
    for (folders_t::reverse_iterator iter = mFolders.rbegin();
         iter != mFolders.rend();++iter)
    {
        LLFolderViewFolder* folder = (*iter);
        if (folder->getVisible())
        {
            LLFolderViewItem* itemp = folder->getPreviousFromChild(0,true);
            if(itemp)
                setSelection(itemp,false,true);
            return true;
        }
    }
    return false;
}


S32 LLFolderView::notify(const LLSD& info)
{
    if(info.has("action"))
    {
        std::string str_action = info["action"];
        if(str_action == "select_first")
        {
            setFocus(true);
            selectFirstItem();
            scrollToShowSelection();
            return 1;

        }
        else if(str_action == "select_last")
        {
            setFocus(true);
            selectLastItem();
            scrollToShowSelection();
            return 1;
        }
    }
    return 0;
}


///----------------------------------------------------------------------------
/// Local function definitions
///----------------------------------------------------------------------------

void LLFolderView::onRenamerLost()
{
    if (mRenamer && mRenamer->getVisible())
    {
        mRenamer->setVisible(false);

        // will commit current name (which could be same as original name)
        mRenamer->setFocus(false);
    }

    if( mRenameItem )
    {
        setSelection( mRenameItem, true );
        mRenameItem = NULL;
    }
}

LLFolderViewItem* LLFolderView::getNextUnselectedItem()
{
    LLFolderViewItem* last_item = *mSelectedItems.rbegin();
    LLFolderViewItem* new_selection = last_item->getNextOpenNode(false);
    while(new_selection && new_selection->isSelected())
    {
        new_selection = new_selection->getNextOpenNode(false);
    }
    if (!new_selection)
    {
        new_selection = last_item->getPreviousOpenNode(false);
        while (new_selection && (new_selection->isInSelection()))
        {
            new_selection = new_selection->getPreviousOpenNode(false);
        }
    }
    return new_selection;
}

S32 LLFolderView::getItemHeight() const
{
    if(!hasVisibleChildren())
{
        //We need to display status textbox, let's reserve some place for it
        return llmax(0, mStatusTextBox->getTextPixelHeight());
}
    return 0;
}