/** 
* @file llfolderviewitem.cpp
* @brief Items and folders that can appear in a hierarchical folder view
*
* $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$
*/

// <FS:Ansariel> Remove ugly dependency
//#include "../newview/llviewerprecompiledheaders.h"

#include "llflashtimer.h"

#include "linden_common.h"
#include "llfolderviewitem.h"
#include "llfolderview.h"
#include "llfolderviewmodel.h"
#include "llpanel.h"
#include "llcriticaldamp.h"
#include "llclipboard.h"
#include "llfocusmgr.h"		// gFocusMgr
#include "lltrans.h"
#include "llwindow.h"

///----------------------------------------------------------------------------
/// Class LLFolderViewItem
///----------------------------------------------------------------------------

static LLDefaultChildRegistry::Register<LLFolderViewItem> r("folder_view_item");

// statics 
std::map<U8, LLFontGL*> LLFolderViewItem::sFonts; // map of styles to fonts

bool LLFolderViewItem::sColorSetInitialized = false;
LLUIColor LLFolderViewItem::sFgColor;
LLUIColor LLFolderViewItem::sHighlightBgColor;
LLUIColor LLFolderViewItem::sFlashBgColor;
LLUIColor LLFolderViewItem::sFocusOutlineColor;
LLUIColor LLFolderViewItem::sMouseOverColor;
LLUIColor LLFolderViewItem::sFilterBGColor;
LLUIColor LLFolderViewItem::sFilterTextColor;
LLUIColor LLFolderViewItem::sSuffixColor;
LLUIColor LLFolderViewItem::sSearchStatusColor;
// <FS:Ansariel> Special for protected items
LLUIColor LLFolderViewItem::sProtectedColor;

// only integers can be initialized in header
const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f;
const F32 LLFolderViewItem::FOLDER_OPEN_TIME_CONSTANT = 0.03f;

const LLColor4U DEFAULT_WHITE(255, 255, 255);


//static
LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style)
{
	LLFontGL* rtn = sFonts[style];
	if (!rtn) // grab label font with this style, lazily
	{
		LLFontDescriptor labelfontdesc("SansSerif", "Small", style);
		rtn = LLFontGL::getFont(labelfontdesc);
		if (!rtn)
		{
			rtn = LLFontGL::getFontDefault();
		}
		sFonts[style] = rtn;
	}
	return rtn;
}

//static
void LLFolderViewItem::initClass()
{
}

//static
void LLFolderViewItem::cleanupClass()
{
	sFonts.clear();
}


// NOTE: Optimize this, we call it a *lot* when opening a large inventory
LLFolderViewItem::Params::Params()
:	root(),
	listener(),
	folder_arrow_image("folder_arrow_image"),
	folder_indentation("folder_indentation"),
	selection_image("selection_image"),
	item_height("item_height"),
	item_top_pad("item_top_pad"),
	creation_date(),
    allow_wear("allow_wear", true),
    allow_drop("allow_drop", true),
	font_color("font_color"),
	font_highlight_color("font_highlight_color"),
    left_pad("left_pad", 0),
    icon_pad("icon_pad", 0),
    icon_width("icon_width", 0),
    text_pad("text_pad", 0),
    text_pad_right("text_pad_right", 0),
    arrow_size("arrow_size", 0),
    max_folder_item_overlap("max_folder_item_overlap", 0),
	// <FS:Ansariel> Inventory specials
	for_inventory("for_inventory", false)
{
}

// Default constructor
LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p)
:	LLView(p),
	mLabelWidth(0),
	mLabelWidthDirty(false),
    mSuffixNeedsRefresh(false),
    mLabelPaddingRight(DEFAULT_LABEL_PADDING_RIGHT),
	mParentFolder( NULL ),
	mIsSelected( FALSE ),
	mIsCurSelection( FALSE ),
	mSelectPending(FALSE),
	mIsItemCut(false),
	mCutGeneration(0),
	mLabelStyle( LLFontGL::NORMAL ),
	mHasVisibleChildren(FALSE),
    mLocalIndentation(p.folder_indentation),
	mIndentation(0),
	mItemHeight(p.item_height),
	mControlLabelRotation(0.f),
	mDragAndDropTarget(FALSE),
	mLabel(p.name),
	mRoot(p.root),
	mViewModelItem(p.listener),
	mIsMouseOverTitle(false),
	mAllowWear(p.allow_wear),
    mAllowDrop(p.allow_drop),
	mFontColor(p.font_color),
	mFontHighlightColor(p.font_highlight_color),
    mLeftPad(p.left_pad),
    mIconPad(p.icon_pad),
    mIconWidth(p.icon_width),
    mTextPad(p.text_pad),
    mTextPadRight(p.text_pad_right),
    mArrowSize(p.arrow_size),
    mMaxFolderItemOverlap(p.max_folder_item_overlap),
	// <FS:Ansariel> Inventory specials
	mForInventory(p.for_inventory),
	mItemTopPad(p.item_top_pad)
{
	if (!sColorSetInitialized)
	{
		// <FS:Ansariel> Make inventory selection color independent from menu color
		//sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
		//sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
		sFgColor = LLUIColorTable::instance().getColor("InventoryItemEnabledColor", DEFAULT_WHITE);
		sHighlightBgColor = LLUIColorTable::instance().getColor("InventoryItemHighlightBgColor", DEFAULT_WHITE);
		// </FS:Ansariel> Make inventory selection color independent from menu color
		sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE);
		sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE);
		sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
		sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE);
		sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE);
		// <FS:Ansariel> Fix misleading color name
		//sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE);
		sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemSuffixColor", DEFAULT_WHITE);
		// </FS:Ansariel>
		sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE);
		// <FS:Ansariel> Special for protected items
		sProtectedColor = LLUIColorTable::instance().getColor("InventoryProtectedColor", DEFAULT_WHITE);
		sColorSetInitialized = true;
	}

	if (mViewModelItem)
	{
		mViewModelItem->setFolderViewItem(this);
	}
}

// Destroys the object
LLFolderViewItem::~LLFolderViewItem()
{
	mViewModelItem = NULL;
}

BOOL LLFolderViewItem::postBuild()
{
    LLFolderViewModelItem& vmi = *getViewModelItem();
    // getDisplayName() is expensive (due to internal getLabelSuffix() and name building)
    // it also sets search strings so it requires a filter reset
    mLabel = vmi.getDisplayName();
    setToolTip(vmi.getName());

    // Dirty the filter flag of the model from the view (CHUI-849)
    vmi.dirtyFilter();

    // Don't do full refresh on constructor if it is possible to avoid
    // it significantly slows down bulk view creation.
    // Todo: Ideally we need to move getDisplayName() out of constructor as well.
    // Like: make a logic that will let filter update search string,
    // while LLFolderViewItem::arrange() updates visual part
    mSuffixNeedsRefresh = true;
    mLabelWidthDirty = true;
	return TRUE;
}

LLFolderView* LLFolderViewItem::getRoot()
{
	return mRoot;
}

const LLFolderView* LLFolderViewItem::getRoot() const
{
	return mRoot;
}
// Returns true if this object is a child (or grandchild, etc.) of potential_ancestor.
BOOL LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor )
{
	LLFolderViewItem* root = this;
	while( root->mParentFolder )
	{
		if( root->mParentFolder == potential_ancestor )
		{
			return TRUE;
		}
		root = root->mParentFolder;
	}
	return FALSE;
}

LLFolderViewItem* LLFolderViewItem::getNextOpenNode(BOOL include_children)
{
	if (!mParentFolder)
	{
		return NULL;
	}

	LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children );
	while(itemp && !itemp->getVisible())
	{
		LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children );
		if (itemp == next_itemp) 
		{
			// hit last item
			return itemp->getVisible() ? itemp : this;
		}
		itemp = next_itemp;
	}

	return itemp;
}

LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(BOOL include_children)
{
	if (!mParentFolder)
	{
		return NULL;
	}

	LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children );

	// Skip over items that are invisible or are hidden from the UI.
	while(itemp && !itemp->getVisible())
	{
		LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children );
		if (itemp == next_itemp) 
		{
			// hit first item
			return itemp->getVisible() ? itemp : this;
		}
		itemp = next_itemp;
	}

	return itemp;
}

BOOL LLFolderViewItem::passedFilter(S32 filter_generation) 
{
	return getViewModelItem()->passedFilter(filter_generation);
}

BOOL LLFolderViewItem::isPotentiallyVisible(S32 filter_generation)
{
	if (filter_generation < 0)
	{
		filter_generation = getFolderViewModel()->getFilter().getFirstSuccessGeneration();
	}
	LLFolderViewModelItem* model = getViewModelItem();
	BOOL visible = model->passedFilter(filter_generation);
	if (model->getMarkedDirtyGeneration() >= filter_generation)
	{
		// unsure visibility state
		// retaining previous visibility until item is updated or filter generation changes
		visible |= getVisible();
	}
	return visible;
}

void LLFolderViewItem::refresh()
{
    LLFolderViewModelItem& vmi = *getViewModelItem();

    mLabel = vmi.getDisplayName();
    setToolTip(vmi.getName());
    // icons are slightly expensive to get, can be optimized
    // see LLInventoryIcon::getIcon()
    mIcon = vmi.getIcon();
    mIconOpen = vmi.getIconOpen();
    mIconOverlay = vmi.getIconOverlay();

    if (mRoot->useLabelSuffix())
    {
        // Very Expensive!
        // Can do a number of expensive checks, like checking active motions, wearables or friend list
        mLabelStyle = vmi.getLabelStyle();
        mLabelSuffix = vmi.getLabelSuffix();
    }

    // Dirty the filter flag of the model from the view (CHUI-849)
    vmi.dirtyFilter();

    mLabelWidthDirty = true;
    mSuffixNeedsRefresh = false;
}

void LLFolderViewItem::refreshSuffix()
{
	LLFolderViewModelItem const* vmi = getViewModelItem();

    // icons are slightly expensive to get, can be optimized
    // see LLInventoryIcon::getIcon()
	mIcon = vmi->getIcon();
    mIconOpen = vmi->getIconOpen();
    mIconOverlay = vmi->getIconOverlay();

	if (mRoot->useLabelSuffix())
	{
        // Very Expensive!
        // Can do a number of expensive checks, like checking active motions, wearables or friend list
        mLabelStyle = vmi->getLabelStyle();
        mLabelSuffix = vmi->getLabelSuffix();
	}

    mLabelWidthDirty = true;
    mSuffixNeedsRefresh = false;
}

// Utility function for LLFolderView
void LLFolderViewItem::arrangeAndSet(BOOL set_selection,
									 BOOL take_keyboard_focus)
{
	LLFolderView* root = getRoot();
	if (getParentFolder())
	{
	getParentFolder()->requestArrange();
	}
	if(set_selection)
	{
		getRoot()->setSelection(this, TRUE, take_keyboard_focus);
		if(root)
		{
			root->scrollToShowSelection();
		}
	}		
}


std::set<LLFolderViewItem*> LLFolderViewItem::getSelectionList() const
{
	std::set<LLFolderViewItem*> selection;
	return selection;
}

// addToFolder() returns TRUE if it succeeds. FALSE otherwise
void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder)
{
	folder->addItem(this); 

	// Compute indentation since parent folder changed
	mIndentation = (getParentFolder())
		? getParentFolder()->getIndentation() + mLocalIndentation
		: 0; 
}


// Finds width and height of this object and its children.  Also
// makes sure that this view and its children are the right size.
S32 LLFolderViewItem::arrange( S32* width, S32* height )
{
	// Only indent deeper items in hierarchy
	
	// <FS:Ansariel> Inventory specials
	//mIndentation = (getParentFolder())
	//	? getParentFolder()->getIndentation() + mLocalIndentation
	//	: 0;

	if (mForInventory)
	{
		mIndentation = (getParentFolder()
						&& getParentFolder()->getParentFolder() )
			? getParentFolder()->getIndentation() + mLocalIndentation
			: 0;
	}
	else
	{
		mIndentation = (getParentFolder())
			? getParentFolder()->getIndentation() + mLocalIndentation
			: 0;
	}
	// </FS:Ansariel>

	if (mLabelWidthDirty)
	{
        if (mSuffixNeedsRefresh)
        {
            // Expensive. But despite refreshing label,
            // it is purely visual, so it is fine to do at our laisure
            refreshSuffix();
        }
		mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + getLabelFontForStyle(mLabelStyle)->getWidth(mLabelSuffix) + mLabelPaddingRight; 
		mLabelWidthDirty = false;
	}

	*width = llmax(*width, mLabelWidth); 

	// determine if we need to use ellipses to avoid horizontal scroll. EXT-719
	bool use_ellipses = getRoot()->getUseEllipses();
	if (use_ellipses)
	{
		// limit to set rect to avoid horizontal scrollbar
		*width = llmin(*width, getRoot()->getRect().getWidth());
	}
	*height = getItemHeight();
	return *height;
}

S32 LLFolderViewItem::getItemHeight()
{
	return mItemHeight;
}

S32 LLFolderViewItem::getLabelXPos()
{
    return getIndentation() + mArrowSize + mTextPad + mIconWidth + mIconPad;
}

S32 LLFolderViewItem::getIconPad()
{
    return mIconPad;
}

S32 LLFolderViewItem::getTextPad()
{
    return mTextPad;
}

// *TODO: This can be optimized a lot by simply recording that it is
// selected in the appropriate places, and assuming that set selection
// means 'deselect' for a leaf item. Do this optimization after
// multiple selection is implemented to make sure it all plays nice
// together.
BOOL LLFolderViewItem::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus)
{
	if (selection == this && !mIsSelected)
	{
		selectItem();
	}
	else if (mIsSelected)	// Deselect everything else.
	{
		deselectItem();
	}
	return mIsSelected;
}

BOOL LLFolderViewItem::changeSelection(LLFolderViewItem* selection, BOOL selected)
{
	if (selection == this)
	{
		if (mIsSelected)
		{
			deselectItem();
		}
		else
		{
			selectItem();
		}
		return TRUE;
	}
	return FALSE;
}

void LLFolderViewItem::deselectItem(void)
{
	mIsSelected = FALSE;
}

void LLFolderViewItem::selectItem(void)
{
	if (mIsSelected == FALSE)
	{
		mIsSelected = TRUE;
		getViewModelItem()->selectItem();
	}
}

BOOL LLFolderViewItem::isMovable()
{
	return getViewModelItem()->isItemMovable();
}

BOOL LLFolderViewItem::isRemovable()
{
	return getViewModelItem()->isItemRemovable();
}

void LLFolderViewItem::destroyView()
{
	getRoot()->removeFromSelectionList(this);

	if (mParentFolder)
	{
		// removeView deletes me
		mParentFolder->extractItem(this);
	}
	delete this;
}

// Call through to the viewed object and return true if it can be
// removed.
//BOOL LLFolderViewItem::removeRecursively(BOOL single_item)
BOOL LLFolderViewItem::remove()
{
	if(!isRemovable())
	{
		return FALSE;
	}
	return getViewModelItem()->removeItem();
}

// Build an appropriate context menu for the item.
void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
{
	getViewModelItem()->buildContextMenu(menu, flags);
}

void LLFolderViewItem::openItem( void )
{
	if (mAllowWear || !getViewModelItem()->isItemWearable())
	{
		getViewModelItem()->openItem();
	}
}

void LLFolderViewItem::rename(const std::string& new_name)
{
	if( !new_name.empty() )
	{
		getViewModelItem()->renameItem(new_name);
	}
}

const std::string& LLFolderViewItem::getName( void ) const
{
	static const std::string noName("");
	return getViewModelItem() ? getViewModelItem()->getName() : noName;
}

// LLView functionality
BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
	if(!mIsSelected)
	{
		getRoot()->setSelection(this, FALSE);
	}
	make_ui_sound("UISndClick");
	return TRUE;
}

BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask )
{
	if (LLView::childrenHandleMouseDown(x, y, mask))
	{
		return TRUE;
	}
	
	// No handler needed for focus lost since this class has no
	// state that depends on it.
	gFocusMgr.setMouseCapture( this );

	if (!mIsSelected)
	{
		if(mask & MASK_CONTROL)
		{
			getRoot()->changeSelection(this, !mIsSelected);
		}
		else if (mask & MASK_SHIFT)
		{
			getParentFolder()->extendSelectionTo(this);
		}
		else
		{
			getRoot()->setSelection(this, FALSE);
		}
		make_ui_sound("UISndClick");
	}
	else
	{
		// If selected, we reserve the decision of deselecting/reselecting to the mouse up moment.
		// This is necessary so we maintain selection consistent when starting a drag.
		mSelectPending = TRUE;
	}

// [SL:KB] - Patch: Inventory-DragDrop | Checked: 2014-02-04 (Catznip-3.6)
	S32 screen_x, screen_y;
	localPointToScreen(x, y, &screen_x, &screen_y);
	getRoot()->setDragStart( screen_x, screen_y );
// [/SL:KB]
//	mDragStartX = x;
//	mDragStartY = y;
	return TRUE;
}

BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
{
	static LLCachedControl<S32> drag_and_drop_threshold(*LLUI::getInstance()->mSettingGroups["config"],"DragAndDropDistanceThreshold", 3);

	mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight));

	if( hasMouseCapture() && isMovable() )
	{
			LLFolderView* root = getRoot();

//		if( (x - mDragStartX) * (x - mDragStartX) + (y - mDragStartY) * (y - mDragStartY) > drag_and_drop_threshold() * drag_and_drop_threshold() 
//			&& root->getAllowDrag()
//			&& root->getCurSelectedItem()
//			&& root->startDrag())
//		{
//					// RN: when starting drag and drop, clear out last auto-open
//					root->autoOpenTest(NULL);
//					root->setShowSelectionContext(TRUE);
//
//					// Release keyboard focus, so that if stuff is dropped into the
//					// world, pressing the delete key won't blow away the inventory
//					// item.
//					gFocusMgr.setKeyboardFocus(NULL);
//
//			getWindow()->setCursor(UI_CURSOR_ARROW);
//		}
//		else if (x != mDragStartX || y != mDragStartY)
//		{
//			getWindow()->setCursor(UI_CURSOR_NOLOCKED);
//		}
// [SL:KB] - Patch: Inventory-DragDrop | Checked: 2014-02-04 (Catznip-3.6)
		S32 screen_x, screen_y;
		localPointToScreen(x, y, &screen_x, &screen_y);

		bool can_drag = true;
		if (root->isOverDragThreshold(screen_x, screen_y) && root->getAllowDrag() && root->getCurSelectedItem())
		{
			if ((can_drag = root->startDrag()))
			{
					// RN: when starting drag and drop, clear out last auto-open
					root->autoOpenTest(NULL);
					root->setShowSelectionContext(TRUE);

					// Release keyboard focus, so that if stuff is dropped into the
					// world, pressing the delete key won't blow away the inventory
					// item.
					gFocusMgr.setKeyboardFocus(NULL);
			}
		}

		if (can_drag)
			getWindow()->setCursor(UI_CURSOR_ARROW);
		else
			getWindow()->setCursor(UI_CURSOR_NOLOCKED);
// [/SL:KB]

		return TRUE;
	}
	else
	{
		getRoot()->setShowSelectionContext(FALSE);
		getWindow()->setCursor(UI_CURSOR_ARROW);
		// let parent handle this then...
		return FALSE;
	}
}


BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask )
{
	openItem();
	return TRUE;
}

BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask )
{
	if (LLView::childrenHandleMouseUp(x, y, mask))
	{
		return TRUE;
	}
	
	// if mouse hasn't moved since mouse down...
	if ( pointInView(x, y) && mSelectPending )
	{
		//...then select
		if(mask & MASK_CONTROL)
		{
			getRoot()->changeSelection(this, !mIsSelected);
		}
		else if (mask & MASK_SHIFT)
		{
			getParentFolder()->extendSelectionTo(this);
		}
		else
		{
			getRoot()->setSelection(this, FALSE);
		}
	}

	mSelectPending = FALSE;

	if( hasMouseCapture() )
	{
		if (getRoot())
		{
		getRoot()->setShowSelectionContext(FALSE);
		}
		gFocusMgr.setMouseCapture( NULL );
	}
	return TRUE;
}

void LLFolderViewItem::onMouseLeave(S32 x, S32 y, MASK mask)
{
	mIsMouseOverTitle = false;
}

BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
										 EDragAndDropType cargo_type,
										 void* cargo_data,
										 EAcceptance* accept,
										 std::string& tooltip_msg)
{
	BOOL handled = FALSE;
	BOOL accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg);
		handled = accepted;
		if (accepted)
		{
			mDragAndDropTarget = TRUE;
			*accept = ACCEPT_YES_MULTI;
		}
		else
		{
			*accept = ACCEPT_NO;
		}
	if(mParentFolder && !handled)
	{
		// store this item to get it in LLFolderBridge::dragItemIntoFolder on drop event.
		mRoot->setDraggingOverItem(this);
		handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg);
		mRoot->setDraggingOverItem(NULL);
	}
	if (handled)
	{
		LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFolderViewItem" << LL_ENDL;
	}

	return handled;
}

void LLFolderViewItem::drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color)
{
	//--------------------------------------------------------------------------------//
	// Draw open folder arrow
	//
	// <FS:Ansariel> Inventory specials
	//const S32 TOP_PAD = default_params.item_top_pad;
	const S32 TOP_PAD = mItemTopPad;

	if (hasVisibleChildren() || !isFolderComplete())
	{
		LLUIImage* arrow_image = default_params.folder_arrow_image;
		gl_draw_scaled_rotated_image(
			mIndentation, getRect().getHeight() - mArrowSize - mTextPad - TOP_PAD,
			mArrowSize, mArrowSize, mControlLabelRotation, arrow_image->getImage(), fg_color);
	}
}

/*virtual*/ bool LLFolderViewItem::isHighlightAllowed()
{
	return mIsSelected;
}

/*virtual*/ bool LLFolderViewItem::isHighlightActive()
{
	return mIsCurSelection;
}

/*virtual*/ bool LLFolderViewItem::isFadeItem()
{
    LLClipboard& clipboard = LLClipboard::instance();
    if (mCutGeneration != clipboard.getGeneration())
    {
        mCutGeneration = clipboard.getGeneration();
        mIsItemCut = clipboard.isCutMode()
                     && ((getParentFolder() && getParentFolder()->isFadeItem())
                        || getViewModelItem()->isCutToClipboard());
    }
    return mIsItemCut;
}

void LLFolderViewItem::drawHighlight(const BOOL showContent, const BOOL hasKeyboardFocus, const LLUIColor &selectColor, const LLUIColor &flashColor,  
                                                        const LLUIColor &focusOutlineColor, const LLUIColor &mouseOverColor)
{
    const S32 focus_top = getRect().getHeight();
    const S32 focus_bottom = getRect().getHeight() - mItemHeight;
    const bool folder_open = (getRect().getHeight() > mItemHeight + 4);
    const S32 FOCUS_LEFT = 1;
	
	// Determine which background color to use for highlighting
	LLUIColor bgColor = (isFlashing() ? flashColor : selectColor);

    //--------------------------------------------------------------------------------//
    // Draw highlight for selected items
	// Note: Always render "current" item or flashing item, only render other selected 
	// items if mShowSingleSelection is FALSE.
    //
    if (isHighlightAllowed())	
    							
    {
        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
		
		// Highlight for selected but not current items
        if (!isHighlightActive() && !isFlashing())
        {
			LLColor4 bg_color = bgColor;
            // do time-based fade of extra objects
            F32 fade_time = (getRoot() ? getRoot()->getSelectionFadeElapsedTime() : 0.0f);
            if (getRoot() && getRoot()->getShowSingleSelection())
            {
                // fading out
                bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f);
            }
            else
            {
                // fading in
                bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]);
            }
        	gl_rect_2d(FOCUS_LEFT,
					   focus_top, 
					   getRect().getWidth() - 2,
					   focus_bottom,
					   bg_color, hasKeyboardFocus);
        }

		// Highlight for currently selected or flashing item
        if (isHighlightActive())
        {
			// Background
        	gl_rect_2d(FOCUS_LEFT,
                focus_top,
                getRect().getWidth() - 2,
                focus_bottom,
                bgColor, hasKeyboardFocus);
			// Outline
            gl_rect_2d(FOCUS_LEFT, 
                focus_top, 
                getRect().getWidth() - 2,
                focus_bottom,
                focusOutlineColor, FALSE);
        }

        if (folder_open)
        {
            gl_rect_2d(FOCUS_LEFT,
                focus_bottom + 1, // overlap with bottom edge of above rect
                getRect().getWidth() - 2,
                0,
                focusOutlineColor, FALSE);
            if (showContent && !isFlashing())
            {
                gl_rect_2d(FOCUS_LEFT,
                    focus_bottom + 1,
                    getRect().getWidth() - 2,
                    0,
                    bgColor, TRUE);
            }
        }
    }
    else if (mIsMouseOverTitle)
    {
        gl_rect_2d(FOCUS_LEFT,
            focus_top, 
            getRect().getWidth() - 2,
            focus_bottom,
            mouseOverColor, FALSE);
    }

    //--------------------------------------------------------------------------------//
    // Draw DragNDrop highlight
    //
    if (mDragAndDropTarget)
    {
        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
        gl_rect_2d(FOCUS_LEFT, 
            focus_top, 
            getRect().getWidth() - 2,
            focus_bottom,
            bgColor, FALSE);
        if (folder_open)
        {
            gl_rect_2d(FOCUS_LEFT,
                focus_bottom + 1, // overlap with bottom edge of above rect
                getRect().getWidth() - 2,
                0,
                bgColor, FALSE);
        }
        mDragAndDropTarget = FALSE;
    }
}

void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x)
{
    //--------------------------------------------------------------------------------//
    // Draw the actual label text
    //
    font->renderUTF8(mLabel, 0, x, y, color,
        LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
        S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, TRUE);
}

void LLFolderViewItem::draw()
{
    const BOOL show_context = (getRoot() ? getRoot()->getShowSelectionContext() : FALSE);
    const BOOL filled = show_context || (getRoot() ? getRoot()->getParentPanel()->hasFocus() : FALSE); // If we have keyboard focus, draw selection filled

	const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>();
	// <FS:Ansariel> Inventory specials
	//const S32 TOP_PAD = default_params.item_top_pad;
	const S32 TOP_PAD = mItemTopPad;
	
	const LLFontGL* font = getLabelFontForStyle(mLabelStyle);

    getViewModelItem()->update();

    drawOpenFolderArrow(default_params, sFgColor);

    drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor);

	//--------------------------------------------------------------------------------//
	// Draw open icon
	//
	const S32 icon_x = mIndentation + mArrowSize + mTextPad;
	if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) // For open folders
 	{
		mIconOpen->draw(icon_x, getRect().getHeight() - mIconOpen->getHeight() - TOP_PAD + 1);
	}
	else if (mIcon)
	{
 		mIcon->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1);
 	}

	if (mIconOverlay && getRoot()->showItemLinkOverlays())
	{
		mIconOverlay->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1);
	}

	//--------------------------------------------------------------------------------//
	// Exit if no label to draw
	//
	if (mLabel.empty())
	{
		return;
	}

	std::string::size_type filter_string_length = mViewModelItem->hasFilterStringMatch() ? mViewModelItem->getFilterStringSize() : 0;
	F32 right_x  = 0;
	F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
	F32 text_left = (F32)getLabelXPos();
	std::string combined_string = mLabel + mLabelSuffix;

	if (filter_string_length > 0)
	{
		S32 left = ll_round(text_left) + font->getWidth(combined_string, 0, mViewModelItem->getFilterStringOffset()) - 2;
		S32 right = left + font->getWidth(combined_string, mViewModelItem->getFilterStringOffset(), filter_string_length) + 2;
		S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD);
		S32 top = getRect().getHeight() - TOP_PAD;

		LLUIImage* box_image = default_params.selection_image;
		LLRect box_rect(left, top, right, bottom);
		box_image->draw(box_rect, sFilterBGColor);
    }

    LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor;

    if (isFadeItem())
    {
         // Fade out item color to indicate it's being cut
         color.mV[VALPHA] *= 0.5f;
    }
    drawLabel(font, text_left, y, color, right_x);

	// <FS:Ansariel> Special for locked items
	if (mViewModelItem->isLocked())
	{
		static const std::string locked_string = " (" + LLTrans::getString("LockedFolder") + ") ";
		font->renderUTF8(locked_string, 0, right_x, y, sProtectedColor,
						 LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, 
						 S32_MAX, S32_MAX, &right_x, FALSE);
	}
	// </FS:Ansariel>

	// <FS:Ansariel> FIRE-29342: Protect folder option
	if (mViewModelItem->isProtected())
	{
		static const std::string protected_string = " (" + LLTrans::getString("ProtectedFolder") + ") ";
		font->renderUTF8(protected_string, 0, right_x, y, sProtectedColor,
						 LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, 
						 S32_MAX, S32_MAX, &right_x, FALSE);
	}
	// </FS:Ansariel>

	//--------------------------------------------------------------------------------//
	// Draw label suffix
	//
	if (!mLabelSuffix.empty())
	{
		font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor,
						  LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
						  S32_MAX, S32_MAX, &right_x, FALSE );
	}

	//--------------------------------------------------------------------------------//
	// Highlight string match
	//
    if (filter_string_length > 0)
    {
        S32 filter_offset = mViewModelItem->getFilterStringOffset();
        F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length);
        F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
        font->renderUTF8( combined_string, filter_offset, match_string_left, yy,
            sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
            filter_string_length, S32_MAX, &right_x, FALSE );
    }

    //Gilbert Linden 9-20-2012: Although this should be legal, removing it because it causes the mLabelSuffix rendering to
    //be distorted...oddly. I initially added this in but didn't need it after all. So removing to prevent unnecessary bug.
    //LLView::draw();
}

const LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) const
{
	return getRoot()->getFolderViewModel();
}
		
LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void )
{
	return getRoot()->getFolderViewModel();
}

bool LLFolderViewItem::isInSelection() const
{
	return mIsSelected || (mParentFolder && mParentFolder->isInSelection());
}

// <FS:ND> Don't bother with unneeded tooltips in inventory
BOOL LLFolderViewItem::handleToolTip(S32 x, S32 y, MASK mask)
{
	if( childrenHandleToolTip( x, y, mask ) )
		return TRUE;

	int nStart = mArrowSize + mTextPad + mIconWidth + mIconPad + mIndentation;
	int nWidth = getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + nStart;

  	if( getRoot()->getParentPanel()->getRect().getWidth() < nWidth ) // Label is truncated, display tooltip
	{
		setToolTip( mLabel );
		return LLView::handleToolTip( x, y, mask );
	}
	else
		setToolTip( LLStringExplicit("") );

	// In case of root we always want to return TRUE, otherwise tooltip handling gets propagated one level up and we end with a tooltip like 'All Items'.
	if( this == getRoot() )
		return TRUE;

	return FALSE;
}
// </FS:ND>


///----------------------------------------------------------------------------
/// Class LLFolderViewFolder
///----------------------------------------------------------------------------

LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): 
	LLFolderViewItem( p ),
	mIsOpen(FALSE),
	mExpanderHighlighted(FALSE),
	mCurHeight(0.f),
	mTargetHeight(0.f),
	mAutoOpenCountdown(0.f),
	mIsFolderComplete(false), // folder might have children that are not loaded yet.
	mAreChildrenInited(false), // folder might have children that are not built yet.
	mLastArrangeGeneration( -1 ),
	mLastCalculatedWidth(0)
{
}

void LLFolderViewFolder::updateLabelRotation()
{
	if (mAutoOpenCountdown != 0.f)
	{
		mControlLabelRotation = mAutoOpenCountdown * -90.f;
	}
	else if (isOpen())
	{
		mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLSmoothInterpolation::getInterpolant(0.04f));
	}
	else
	{
		mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLSmoothInterpolation::getInterpolant(0.025f));
	}
}

// Destroys the object
LLFolderViewFolder::~LLFolderViewFolder( void )
{
	// The LLView base class takes care of object destruction. make sure that we
	// don't have mouse or keyboard focus
	gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
}

// addToFolder() returns TRUE if it succeeds. FALSE otherwise
void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder)
{
	folder->addFolder(this);

	// Compute indentation since parent folder changed
	mIndentation = (getParentFolder())
		? getParentFolder()->getIndentation() + mLocalIndentation
		: 0; 

	if(isOpen() && folder->isOpen())
	{
		requestArrange();
	}
}

static LLTrace::BlockTimerStatHandle FTM_ARRANGE("Arrange");

// Make everything right and in the right place ready for drawing (CHUI-849)
// * Sort everything correctly if necessary
// * Turn widgets visible/invisible according to their model filtering state
// * Takes animation state into account for opening/closing of folders (this makes widgets visible/invisible)
// * Reposition visible widgets so that they line up correctly with no gap
// * Compute the width and height of the current folder and its children
// * Makes sure that this view and its children are the right size
S32 LLFolderViewFolder::arrange( S32* width, S32* height )
{
	// Sort before laying out contents
    // Note that we sort from the root (CHUI-849)
    if (mAreChildrenInited)
    {
        getRoot()->getFolderViewModel()->sort(this);
    }

	LL_RECORD_BLOCK_TIME(FTM_ARRANGE);
	
	// evaluate mHasVisibleChildren
	mHasVisibleChildren = false;
	if (mAreChildrenInited && getViewModelItem()->descendantsPassedFilter())
	{
		// We have to verify that there's at least one child that's not filtered out
		bool found = false;
		// Try the items first
		for (items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit)
		{
			LLFolderViewItem* itemp = (*iit);
			found = itemp->isPotentiallyVisible();
			if (found)
				break;
		}
		if (!found)
		{
			// If no item found, try the folders
			for (folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit)
			{
				LLFolderViewFolder* folderp = (*fit);
				found = folderp->isPotentiallyVisible();
				if (found)
					break;
			}
		}

		mHasVisibleChildren = found;
	}
	if (!mIsFolderComplete && mAreChildrenInited)
	{
		mIsFolderComplete = getFolderViewModel()->isFolderComplete(this);
	}



	// calculate height as a single item (without any children), and reshapes rectangle to match
	LLFolderViewItem::arrange( width, height );

	// clamp existing animated height so as to never get smaller than a single item
	mCurHeight = llmax((F32)*height, mCurHeight);

	// initialize running height value as height of single item in case we have no children
	F32 running_height = (F32)*height;
	F32 target_height = (F32)*height;

	// are my children visible?
	if (needsArrange())
	{
		// set last arrange generation first, in case children are animating
		// and need to be arranged again
		mLastArrangeGeneration = getRoot()->getArrangeGeneration();
		if (isOpen())
		{
			// Add sizes of children
			S32 parent_item_height = getRect().getHeight();

			for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit)
			{
				LLFolderViewFolder* folderp = (*fit);
				folderp->setVisible(folderp->isPotentiallyVisible());

				if (folderp->getVisible())
				{
					S32 child_width = *width;
					S32 child_height = 0;
					S32 child_top = parent_item_height - ll_round(running_height);

					target_height += folderp->arrange( &child_width, &child_height );

					running_height += (F32)child_height;
					*width = llmax(*width, child_width);
					folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() );
				}
			}
			for(items_t::iterator iit = mItems.begin();
				iit != mItems.end(); ++iit)
			{
				LLFolderViewItem* itemp = (*iit);
				itemp->setVisible(itemp->isPotentiallyVisible());

				if (itemp->getVisible())
				{
					S32 child_width = *width;
					S32 child_height = 0;
					S32 child_top = parent_item_height - ll_round(running_height);

					target_height += itemp->arrange( &child_width, &child_height );
					// don't change width, as this item is as wide as its parent folder by construction
					itemp->reshape( itemp->getRect().getWidth(), child_height);

					running_height += (F32)child_height;
					*width = llmax(*width, child_width);
					itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() );
				}
			}
		}

		mTargetHeight = target_height;
		// cache this width so next time we can just return it
		mLastCalculatedWidth = *width;
	}
	else
	{
		// just use existing width
		*width = mLastCalculatedWidth;
	}

	// animate current height towards target height
	if (llabs(mCurHeight - mTargetHeight) > 1.f)
	{
		mCurHeight = lerp(mCurHeight, mTargetHeight, LLSmoothInterpolation::getInterpolant(isOpen() ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT));

		requestArrange();

		// hide child elements that fall out of current animated height
		for (folders_t::iterator iter = mFolders.begin();
			iter != mFolders.end();)
		{
			folders_t::iterator fit = iter++;
			// number of pixels that bottom of folder label is from top of parent folder
			if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight() 
				> ll_round(mCurHeight) + mMaxFolderItemOverlap)
			{
				// hide if beyond current folder height
				(*fit)->setVisible(FALSE);
			}
		}

		for (items_t::iterator iter = mItems.begin();
			iter != mItems.end();)
		{
			items_t::iterator iit = iter++;
			// number of pixels that bottom of item label is from top of parent folder
			if (getRect().getHeight() - (*iit)->getRect().mBottom
				> ll_round(mCurHeight) + mMaxFolderItemOverlap)
			{
				(*iit)->setVisible(FALSE);
			}
		}
	}
	else
	{
		mCurHeight = mTargetHeight;
	}

	// don't change width as this item is already as wide as its parent folder
	reshape(getRect().getWidth(),ll_round(mCurHeight));

	// pass current height value back to parent
	*height = ll_round(mCurHeight);

	return ll_round(mTargetHeight);
}

BOOL LLFolderViewFolder::needsArrange()
{
	return mLastArrangeGeneration < getRoot()->getArrangeGeneration();
}

bool LLFolderViewFolder::descendantsPassedFilter(S32 filter_generation)
{
	return getViewModelItem()->descendantsPassedFilter(filter_generation);
}

// Passes selection information on to children and record selection
// information if necessary.
BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL openitem,
                                      BOOL take_keyboard_focus)
{
	BOOL rv = FALSE;
	if (selection == this)
	{
		if (!isSelected())
		{
			selectItem();
		}
		rv = TRUE;
	}
	else
	{
		if (isSelected())
		{
			deselectItem();
		}
		rv = FALSE;
	}
	BOOL child_selected = FALSE;

	for (folders_t::iterator iter = mFolders.begin();
		iter != mFolders.end();)
	{
		folders_t::iterator fit = iter++;
		if((*fit)->setSelection(selection, openitem, take_keyboard_focus))
		{
			rv = TRUE;
			child_selected = TRUE;
		}
	}
	for (items_t::iterator iter = mItems.begin();
		iter != mItems.end();)
	{
		items_t::iterator iit = iter++;
		if((*iit)->setSelection(selection, openitem, take_keyboard_focus))
		{
			rv = TRUE;
			child_selected = TRUE;
		}
	}
	if(openitem && child_selected)
	{
		setOpenArrangeRecursively(TRUE);
	}
	return rv;
}

// This method is used to change the selection of an item.
// Recursively traverse all children; if 'selection' is 'this' then change
// the select status if necessary.
// Returns TRUE if the selection state of this folder, or of a child, was changed.
BOOL LLFolderViewFolder::changeSelection(LLFolderViewItem* selection, BOOL selected)
{
	BOOL rv = FALSE;
	if(selection == this)
	{
		if (isSelected() != selected)
		{
			rv = TRUE;
			if (selected)
			{
				selectItem();
			}
			else
			{
				deselectItem();
			}
		}
	}

	for (folders_t::iterator iter = mFolders.begin();
		iter != mFolders.end();)
	{
		folders_t::iterator fit = iter++;
		if((*fit)->changeSelection(selection, selected))
		{
			rv = TRUE;
		}
	}
	for (items_t::iterator iter = mItems.begin();
		iter != mItems.end();)
	{
		items_t::iterator iit = iter++;
		if((*iit)->changeSelection(selection, selected))
		{
			rv = TRUE;
		}
	}
	return rv;
}

LLFolderViewFolder* LLFolderViewFolder::getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse)
{
	if (!item_a->getParentFolder() || !item_b->getParentFolder()) return NULL;

	std::deque<LLFolderViewFolder*> item_a_ancestors;

	LLFolderViewFolder* parent = item_a->getParentFolder();
	while(parent)
	{
		item_a_ancestors.push_back(parent);
		parent = parent->getParentFolder();
	}

	std::deque<LLFolderViewFolder*> item_b_ancestors;
	
	parent = item_b->getParentFolder();
	while(parent)
	{
		item_b_ancestors.push_back(parent);
		parent = parent->getParentFolder();
	}

	LLFolderViewFolder* common_ancestor = item_a->getRoot();

	while(item_a_ancestors.size() > item_b_ancestors.size())
	{
		item_a = item_a_ancestors.front();
		item_a_ancestors.pop_front();
	}

	while(item_b_ancestors.size() > item_a_ancestors.size())
	{
		item_b = item_b_ancestors.front();
		item_b_ancestors.pop_front();
	}

	while(item_a_ancestors.size())
	{
		common_ancestor = item_a_ancestors.front();

		if (item_a_ancestors.front() == item_b_ancestors.front())
		{
			// which came first, sibling a or sibling b?
			for (folders_t::iterator it = common_ancestor->mFolders.begin(), end_it = common_ancestor->mFolders.end();
				it != end_it;
				++it)
			{
				LLFolderViewItem* item = *it;

				if (item == item_a)
				{
					reverse = false;
					return common_ancestor;
				}
				if (item == item_b)
				{
					reverse = true;
					return common_ancestor;
				}
			}

			for (items_t::iterator it = common_ancestor->mItems.begin(), end_it = common_ancestor->mItems.end();
				it != end_it;
				++it)
			{
				LLFolderViewItem* item = *it;

				if (item == item_a)
				{
					reverse = false;
					return common_ancestor;
				}
				if (item == item_b)
				{
					reverse = true;
					return common_ancestor;
				}
			}
			break;
		}

		item_a = item_a_ancestors.front();
		item_a_ancestors.pop_front();
		item_b = item_b_ancestors.front();
		item_b_ancestors.pop_front();
	}

	return NULL;
}

void LLFolderViewFolder::gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector<LLFolderViewItem*>& items)
{
	bool selecting = start == NULL;
	if (reverse)
	{
		for (items_t::reverse_iterator it = mItems.rbegin(), end_it = mItems.rend();
			it != end_it;
			++it)
		{
			if (*it == end)
			{
				return;
			}
			if (selecting && (*it)->getVisible())
			{
				items.push_back(*it);
			}

			if (*it == start)
			{
				selecting = true;
			}
		}
		for (folders_t::reverse_iterator it = mFolders.rbegin(), end_it = mFolders.rend();
			it != end_it;
			++it)
		{
			if (*it == end)
			{
				return;
			}

			if (selecting && (*it)->getVisible())
			{
				items.push_back(*it);
			}

			if (*it == start)
			{
				selecting = true;
			}
		}
	}
	else
	{
		for (folders_t::iterator it = mFolders.begin(), end_it = mFolders.end();
			it != end_it;
			++it)
		{
			if (*it == end)
			{
				return;
			}

			if (selecting && (*it)->getVisible())
			{
				items.push_back(*it);
			}

			if (*it == start)
			{
				selecting = true;
			}
		}
		for (items_t::iterator it = mItems.begin(), end_it = mItems.end();
			it != end_it;
			++it)
		{
			if (*it == end)
			{
				return;
			}

			if (selecting && (*it)->getVisible())
			{
				items.push_back(*it);
			}

			if (*it == start)
			{
				selecting = true;
			}
		}
	}
}

void LLFolderViewFolder::extendSelectionTo(LLFolderViewItem* new_selection)
{
	if (getRoot()->getAllowMultiSelect() == FALSE) return;

	LLFolderViewItem* cur_selected_item = getRoot()->getCurSelectedItem();
	if (cur_selected_item == NULL)
	{
		cur_selected_item = new_selection;
	}


	bool reverse = false;
	LLFolderViewFolder* common_ancestor = getCommonAncestor(cur_selected_item, new_selection, reverse);
	if (!common_ancestor) return;

	LLFolderViewItem* last_selected_item_from_cur = cur_selected_item;
	LLFolderViewFolder* cur_folder = cur_selected_item->getParentFolder();

	std::vector<LLFolderViewItem*> items_to_select_forward;

	while(cur_folder != common_ancestor)
	{
		cur_folder->gatherChildRangeExclusive(last_selected_item_from_cur, NULL, reverse, items_to_select_forward);
			
		last_selected_item_from_cur = cur_folder;
		cur_folder = cur_folder->getParentFolder();
	}

	std::vector<LLFolderViewItem*> items_to_select_reverse;

	LLFolderViewItem* last_selected_item_from_new = new_selection;
	cur_folder = new_selection->getParentFolder();
	while(cur_folder != common_ancestor)
	{
		cur_folder->gatherChildRangeExclusive(last_selected_item_from_new, NULL, !reverse, items_to_select_reverse);

		last_selected_item_from_new = cur_folder;
		cur_folder = cur_folder->getParentFolder();
	}

	common_ancestor->gatherChildRangeExclusive(last_selected_item_from_cur, last_selected_item_from_new, reverse, items_to_select_forward);

	for (std::vector<LLFolderViewItem*>::reverse_iterator it = items_to_select_reverse.rbegin(), end_it = items_to_select_reverse.rend();
		it != end_it;
		++it)
	{
		items_to_select_forward.push_back(*it);
	}

	LLFolderView* root = getRoot();

	BOOL selection_reverse = new_selection->isSelected(); //indication that some elements are being deselected

	// array always go from 'will be selected' to ' will be unselected', iterate
	// in opposite direction to simplify identification of 'point of origin' in
	// case it is in the list we are working with
	for (std::vector<LLFolderViewItem*>::reverse_iterator it = items_to_select_forward.rbegin(), end_it = items_to_select_forward.rend();
		it != end_it;
		++it)
	{
		LLFolderViewItem* item = *it;
		BOOL selected = item->isSelected();
		if (!selection_reverse && selected)
		{
			// it is our 'point of origin' where we shift/expand from
			// don't deselect it
			selection_reverse = TRUE;
		}
		else
		{
			root->changeSelection(item, !selected);
		}
	}

	if (selection_reverse)
	{
		// at some point we reversed selection, first element should be deselected
		root->changeSelection(last_selected_item_from_cur, FALSE);
	}

	// element we expand to should always be selected
	root->changeSelection(new_selection, TRUE);
}


void LLFolderViewFolder::destroyView()
{
    while (!mItems.empty())
    {
    	LLFolderViewItem *itemp = mItems.back();
        mItems.pop_back();
    	itemp->destroyView(); // LLFolderViewItem::destroyView() removes entry from mItems
    }

	while (!mFolders.empty())
	{
		LLFolderViewFolder *folderp = mFolders.back();
        mFolders.pop_back();
		folderp->destroyView(); // LLFolderVievFolder::destroyView() removes entry from mFolders
	}

	LLFolderViewItem::destroyView();
}

// extractItem() removes the specified item from the folder, but
// doesn't delete it.
void LLFolderViewFolder::extractItem( LLFolderViewItem* item, bool deparent_model )
{
	if (item->isSelected())
		getRoot()->clearSelection();
	items_t::iterator it = std::find(mItems.begin(), mItems.end(), item);
	if(it == mItems.end())
	{
		// This is an evil downcast. However, it's only doing
		// pointer comparison to find if (which it should be ) the
		// item is in the container, so it's pretty safe.
		LLFolderViewFolder* f = static_cast<LLFolderViewFolder*>(item);
		folders_t::iterator ft;
		ft = std::find(mFolders.begin(), mFolders.end(), f);
		if (ft != mFolders.end())
		{
			mFolders.erase(ft);
		}
	}
	else
	{
		mItems.erase(it);
	}
	//item has been removed, need to update filter
    if (deparent_model)
    {
        // in some cases model does not belong to parent view, is shared between views
        getViewModelItem()->removeChild(item->getViewModelItem());
    }
	//because an item is going away regardless of filter status, force rearrange
	requestArrange();
	removeChild(item);
}

BOOL LLFolderViewFolder::isMovable()
{
	if( !(getViewModelItem()->isItemMovable()) )
	{
			return FALSE;
		}

		for (items_t::iterator iter = mItems.begin();
			iter != mItems.end();)
		{
			items_t::iterator iit = iter++;
			if(!(*iit)->isMovable())
			{
				return FALSE;
			}
		}

		for (folders_t::iterator iter = mFolders.begin();
			iter != mFolders.end();)
		{
			folders_t::iterator fit = iter++;
			if(!(*fit)->isMovable())
			{
				return FALSE;
			}
		}
	return TRUE;
}


BOOL LLFolderViewFolder::isRemovable()
{
	if( !(getViewModelItem()->isItemRemovable()) )
	{
			return FALSE;
		}

		for (items_t::iterator iter = mItems.begin();
			iter != mItems.end();)
		{
			items_t::iterator iit = iter++;
			if(!(*iit)->isRemovable())
			{
				return FALSE;
			}
		}

		for (folders_t::iterator iter = mFolders.begin();
			iter != mFolders.end();)
		{
			folders_t::iterator fit = iter++;
			if(!(*fit)->isRemovable())
			{
				return FALSE;
			}
		}
	return TRUE;
}

// this is an internal method used for adding items to folders. 
void LLFolderViewFolder::addItem(LLFolderViewItem* item)
{
	if (item->getParentFolder())
	{
		item->getParentFolder()->extractItem(item);
	}
	item->setParentFolder(this);

	mItems.push_back(item);
	
	item->setRect(LLRect(0, 0, getRect().getWidth(), 0));
	item->setVisible(FALSE);
	
	addChild(item);
	
	// When the model is already hooked into a hierarchy (i.e. has a parent), do not reparent it
	// Note: this happens when models are created before views or shared between views
	if (!item->getViewModelItem()->hasParent())
	{
		getViewModelItem()->addChild(item->getViewModelItem());
	}
}

// this is an internal method used for adding items to folders. 
void LLFolderViewFolder::addFolder(LLFolderViewFolder* folder)
{
	if (folder->mParentFolder)
	{
		folder->mParentFolder->extractItem(folder);
	}
	folder->mParentFolder = this;
	mFolders.push_back(folder);
	folder->setOrigin(0, 0);
	folder->reshape(getRect().getWidth(), 0);
	folder->setVisible(FALSE);
	// rearrange all descendants too, as our indentation level might have changed
	//folder->requestArrange();
	//requestSort();

	addChild(folder);

	// When the model is already hooked into a hierarchy (i.e. has a parent), do not reparent it
	// Note: this happens when models are created before views or shared between views
	if (!folder->getViewModelItem()->hasParent())
	{
		getViewModelItem()->addChild(folder->getViewModelItem());
	}
}

void LLFolderViewFolder::requestArrange()
{
    mLastArrangeGeneration = -1;
    // flag all items up to root
    if (mParentFolder)
    {
        mParentFolder->requestArrange();
    }
}

void LLFolderViewFolder::toggleOpen()
{
	setOpen(!isOpen());
}

// Force a folder open or closed
void LLFolderViewFolder::setOpen(BOOL openitem)
{
	setOpenArrangeRecursively(openitem);
}

void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse)
{
	BOOL was_open = isOpen();
	mIsOpen = openitem;
		if(!was_open && openitem)
		{
			getViewModelItem()->openItem();
			// openItem() will request content, it won't be incomplete
			mIsFolderComplete = true;
		}
		else if(was_open && !openitem)
		{
		getViewModelItem()->closeItem();
	}

	if (recurse == RECURSE_DOWN || recurse == RECURSE_UP_DOWN)
	{
		for (folders_t::iterator iter = mFolders.begin();
			iter != mFolders.end();)
		{
			folders_t::iterator fit = iter++;
			(*fit)->setOpenArrangeRecursively(openitem, RECURSE_DOWN);		/* Flawfinder: ignore */
		}
	}
	if (mParentFolder
		&&	(recurse == RECURSE_UP
			|| recurse == RECURSE_UP_DOWN))
	{
		mParentFolder->setOpenArrangeRecursively(openitem, RECURSE_UP);
	}

	if (was_open != isOpen())
	{
		requestArrange();
	}
}

BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask,
													BOOL drop,
													EDragAndDropType c_type,
													void* cargo_data,
													EAcceptance* accept,
													std::string& tooltip_msg)
{
	BOOL accepted = mViewModelItem->dragOrDrop(mask,drop,c_type,cargo_data, tooltip_msg);
	if (accepted) 
	{
		mDragAndDropTarget = TRUE;
		*accept = ACCEPT_YES_MULTI;
	}
	else 
	{
		*accept = ACCEPT_NO;
	}

	// drag and drop to child item, so clear pending auto-opens
	getRoot()->autoOpenTest(NULL);

	return TRUE;
}

void LLFolderViewFolder::openItem( void )
{
	toggleOpen();
}

void LLFolderViewFolder::applyFunctorToChildren(LLFolderViewFunctor& functor)
{
	for (folders_t::iterator iter = mFolders.begin();
		iter != mFolders.end();)
	{
		folders_t::iterator fit = iter++;
		functor.doItem((*fit));
	}
	for (items_t::iterator iter = mItems.begin();
		iter != mItems.end();)
	{
		items_t::iterator iit = iter++;
		functor.doItem((*iit));
	}
}

void LLFolderViewFolder::applyFunctorRecursively(LLFolderViewFunctor& functor)
{
	functor.doFolder(this);

	for (folders_t::iterator iter = mFolders.begin();
		iter != mFolders.end();)
	{
		folders_t::iterator fit = iter++;
		(*fit)->applyFunctorRecursively(functor);
	}
	for (items_t::iterator iter = mItems.begin();
		iter != mItems.end();)
	{
		items_t::iterator iit = iter++;
		functor.doItem((*iit));
	}
}

// LLView functionality
BOOL LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask,
										   BOOL drop,
										   EDragAndDropType cargo_type,
										   void* cargo_data,
										   EAcceptance* accept,
										   std::string& tooltip_msg)
{
	BOOL handled = FALSE;

	if (isOpen())
	{
		handled = (childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL);
	}

	if (!handled)
	{
		handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg);

		LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFolderViewFolder" << LL_ENDL;
	}

	return TRUE;
}

BOOL LLFolderViewFolder::handleDragAndDropToThisFolder(MASK mask,
													   BOOL drop,
													   EDragAndDropType cargo_type,
													   void* cargo_data,
													   EAcceptance* accept,
													   std::string& tooltip_msg)
{
    if (!mAllowDrop)
    {
		*accept = ACCEPT_NO;
        tooltip_msg = LLTrans::getString("TooltipOutboxCannotDropOnRoot");
        return TRUE;
    }
    
	BOOL accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg);

	if (accepted)
	{
		mDragAndDropTarget = TRUE;
		*accept = ACCEPT_YES_MULTI;
	}
	else 
	{
		*accept = ACCEPT_NO;
	}
	
	if (!drop && accepted)
	{
		getRoot()->autoOpenTest(this);
	}
	
	return TRUE;
}


BOOL LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
	BOOL handled = FALSE;

	if( isOpen() )
	{
		handled = childrenHandleRightMouseDown( x, y, mask ) != NULL;
	}
	if (!handled)
	{
		handled = LLFolderViewItem::handleRightMouseDown( x, y, mask );
	}
	return handled;
}


BOOL LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask)
{
	mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight));

	BOOL handled = LLView::handleHover(x, y, mask);

	if (!handled)
	{
		// this doesn't do child processing
		handled = LLFolderViewItem::handleHover(x, y, mask);
	}

	return handled;
}

BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask )
{
	BOOL handled = FALSE;
	if( isOpen() )
	{
		handled = childrenHandleMouseDown(x,y,mask) != NULL;
	}
	if( !handled )
	{
		if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad)
		{
			toggleOpen();
			handled = TRUE;
		}
		else
		{
			// do normal selection logic
			handled = LLFolderViewItem::handleMouseDown(x, y, mask);
		}
	}

	return handled;
}

BOOL LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask )
{
	BOOL handled = FALSE;
	if( isOpen() )
	{
		handled = childrenHandleDoubleClick( x, y, mask ) != NULL;
	}
	if( !handled )
	{
		if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad)
		{
			// don't select when user double-clicks plus sign
			// so as not to contradict single-click behavior
			toggleOpen();
		}
		else
		{
			getRoot()->setSelection(this, FALSE);
			toggleOpen();
		}
		handled = TRUE;
	}
	return handled;
}

void LLFolderViewFolder::draw()
{
	updateLabelRotation();

	LLFolderViewItem::draw();

	// draw children if root folder, or any other folder that is open or animating to closed state
	if( getRoot() == this || (isOpen() || mCurHeight != mTargetHeight ))
	{
		LLView::draw();
	}

	mExpanderHighlighted = FALSE;
}

// this does prefix traversal, as folders are listed above their contents
LLFolderViewItem* LLFolderViewFolder::getNextFromChild( LLFolderViewItem* item, BOOL include_children )
{
	BOOL found_item = FALSE;

	LLFolderViewItem* result = NULL;
	// when not starting from a given item, start at beginning
	if(item == NULL)
	{
		found_item = TRUE;
	}

	// find current item among children
	folders_t::iterator fit = mFolders.begin();
	folders_t::iterator fend = mFolders.end();

	items_t::iterator iit = mItems.begin();
	items_t::iterator iend = mItems.end();

	// if not trivially starting at the beginning, we have to find the current item
	if (!found_item)
	{
		// first, look among folders, since they are always above items
		for(; fit != fend; ++fit)
		{
			if(item == (*fit))
			{
				found_item = TRUE;
				// if we are on downwards traversal
				if (include_children && (*fit)->isOpen())
				{
					// look for first descendant
					return (*fit)->getNextFromChild(NULL, TRUE);
				}
				// otherwise advance to next folder
				++fit;
				include_children = TRUE;
				break;
			}
		}

		// didn't find in folders?  Check items...
		if (!found_item)
		{
			for(; iit != iend; ++iit)
			{
				if(item == (*iit))
				{
					found_item = TRUE;
					// point to next item
					++iit;
					break;
				}
			}
		}
	}

	if (!found_item)
	{
		// you should never call this method with an item that isn't a child
		// so we should always find something
		llassert(FALSE);
		return NULL;
	}

	// at this point, either iit or fit point to a candidate "next" item
	// if both are out of range, we need to punt up to our parent

	// now, starting from found folder, continue through folders
	// searching for next visible folder
	while(fit != fend && !(*fit)->getVisible())
	{
		// turn on downwards traversal for next folder
		++fit;
	} 

	if (fit != fend)
	{
		result = (*fit);
	}
	else
	{
		// otherwise, scan for next visible item
		while(iit != iend && !(*iit)->getVisible())
		{
			++iit;
		} 

		// check to see if we have a valid item
		if (iit != iend)
		{
			result = (*iit);
		}
	}

	if( !result && mParentFolder )
	{
		// If there are no siblings or children to go to, recurse up one level in the tree
		// and skip children for this folder, as we've already discounted them
		result = mParentFolder->getNextFromChild(this, FALSE);
	}

	return result;
}

// this does postfix traversal, as folders are listed above their contents
LLFolderViewItem* LLFolderViewFolder::getPreviousFromChild( LLFolderViewItem* item, BOOL include_children )
{
	BOOL found_item = FALSE;

	LLFolderViewItem* result = NULL;
	// when not starting from a given item, start at end
	if(item == NULL)
	{
		found_item = TRUE;
	}

	// find current item among children
	folders_t::reverse_iterator fit = mFolders.rbegin();
	folders_t::reverse_iterator fend = mFolders.rend();

	items_t::reverse_iterator iit = mItems.rbegin();
	items_t::reverse_iterator iend = mItems.rend();

	// if not trivially starting at the end, we have to find the current item
	if (!found_item)
	{
		// first, look among items, since they are always below the folders
		for(; iit != iend; ++iit)
		{
			if(item == (*iit))
			{
				found_item = TRUE;
				// point to next item
				++iit;
				break;
			}
		}

		// didn't find in items?  Check folders...
		if (!found_item)
		{
			for(; fit != fend; ++fit)
			{
				if(item == (*fit))
				{
					found_item = TRUE;
					// point to next folder
					++fit;
					break;
				}
			}
		}
	}

	if (!found_item)
	{
		// you should never call this method with an item that isn't a child
		// so we should always find something
		llassert(FALSE);
		return NULL;
	}

	// at this point, either iit or fit point to a candidate "next" item
	// if both are out of range, we need to punt up to our parent

	// now, starting from found item, continue through items
	// searching for next visible item
	while(iit != iend && !(*iit)->getVisible())
	{
		++iit;
	} 

	if (iit != iend)
	{
		// we found an appropriate item
		result = (*iit);
	}
	else
	{
		// otherwise, scan for next visible folder
		while(fit != fend && !(*fit)->getVisible())
		{
			++fit;
		} 

		// check to see if we have a valid folder
		if (fit != fend)
		{
			// try selecting child element of this folder
			if ((*fit)->isOpen() && include_children)
			{
				result = (*fit)->getPreviousFromChild(NULL);
			}
			else
			{
				result = (*fit);
			}
		}
	}

	if( !result )
	{
		// If there are no siblings or children to go to, recurse up one level in the tree
		// which gets back to this folder, which will only be visited if it is a valid, visible item
		result = this;
	}

	return result;
}

