Files
SingularityViewer/indra/newview/llfolderviewitem.cpp
2017-03-14 01:44:26 -05:00

2888 lines
71 KiB
C++

/**
* @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$
*/
#include "llviewerprecompiledheaders.h"
#include "llfolderviewitem.h"
// viewer includes
#include "llfolderview.h" // Items depend extensively on LLFolderViews
#include "llfoldervieweventlistener.h"
#include "llviewerfoldertype.h"
#include "llinventorybridge.h" // for LLItemBridge in LLInventorySort::operator()
#include "llinventoryfilter.h"
#include "llinventoryfunctions.h"
#include "llinventorymodelbackgroundfetch.h"
#include "llresmgr.h"
#include "llpanel.h"
#include "llviewercontrol.h" // gSavedSettings
#include "llviewerwindow.h" // Argh, only for setCursor()
// linden library includes
#include "llfocusmgr.h" // gFocusMgr
#include "lltrans.h"
// statics
std::map<U8, LLFontGL*> LLFolderViewItem::sFonts; // map of styles to fonts
// 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;
LLUIImagePtr LLFolderViewItem::sArrowImage;
LLUIImagePtr LLFolderViewItem::sBoxImage;
//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::getFontMonospace();
}
sFonts[style] = rtn;
}
return rtn;
}
//static
void LLFolderViewItem::initClass()
{
sArrowImage = LLUI::getUIImage("folder_arrow.tga");
sBoxImage = LLUI::getUIImage("Rounded_Square");
}
//static
void LLFolderViewItem::cleanupClass()
{
sFonts.clear();
sArrowImage = NULL;
sBoxImage = NULL;
}
// Default constructor
// NOTE: Optimize this, we call it a *lot* when opening a large inventory
LLFolderViewItem::LLFolderViewItem( const std::string& name, LLUIImagePtr icon,
LLUIImagePtr icon_open,
LLUIImagePtr icon_overlay,
S32 creation_date,
LLFolderView* root,
LLFolderViewEventListener* listener ) :
LLUICtrl( name, LLRect(0, 0, 0, 0), TRUE, NULL, FOLLOWS_LEFT|FOLLOWS_TOP|FOLLOWS_RIGHT),
mLabelWidth(0),
mLabelWidthDirty(false),
mParentFolder( NULL ),
mIsSelected( FALSE ),
mIsCurSelection( FALSE ),
mSelectPending(FALSE),
mLabelStyle( LLFontGL::NORMAL ),
mHasVisibleChildren(FALSE),
mIndentation(0),
mItemHeight(16 + ICON_PAD),
mPassedFilter(FALSE),
mLastFilterGeneration(-1),
mStringMatchOffset(std::string::npos),
mControlLabelRotation(0.f),
mDragAndDropTarget(FALSE),
mIsLoading(FALSE),
mLabel( name ),
mRoot( root ),
mCreationDate(creation_date),
mIcon(icon),
mIconOpen(icon_open),
mIconOverlay(icon_overlay),
mListener(listener),
mShowLoadStatus(true),
mSearchType(0)
{
postBuild();//Not parsing xml file yet.
}
BOOL LLFolderViewItem::postBuild()
{
refresh();
setTabStop(FALSE);
return TRUE;
}
// Destroys the object
LLFolderViewItem::~LLFolderViewItem( void )
{
delete mListener;
mListener = NULL;
}
LLFolderView* LLFolderViewItem::getRoot()
{
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;
}
// is this item something we think we should be showing?
// for example, if we haven't gotten around to filtering it yet, then the answer is yes
// until we find out otherwise
BOOL LLFolderViewItem::potentiallyVisible()
{
// we haven't been checked against min required filter
// or we have and we passed
return getLastFilterGeneration() < getRoot()->getFilter()->getFirstSuccessGeneration() || getFiltered();
}
BOOL LLFolderViewItem::getFiltered()
{
return mPassedFilter && mLastFilterGeneration >= getRoot()->getFilter()->getFirstSuccessGeneration();
}
BOOL LLFolderViewItem::getFiltered(S32 filter_generation)
{
return mPassedFilter && mLastFilterGeneration >= filter_generation;
}
void LLFolderViewItem::setFiltered(BOOL filtered, S32 filter_generation)
{
mPassedFilter = filtered;
mLastFilterGeneration = filter_generation;
}
void LLFolderViewItem::setIcon(LLUIImagePtr icon)
{
mIcon = icon;
}
// refresh information from the listener
void LLFolderViewItem::refreshFromListener()
{
if(mListener)
{
mLabel = mListener->getDisplayName();
setIcon(mListener->getIcon());
time_t creation_date = mListener->getCreationDate();
if ((creation_date > 0) && (mCreationDate != creation_date))
{
setCreationDate(creation_date);
dirtyFilter();
}
if (mRoot->useLabelSuffix())
{
mLabelStyle = mListener->getLabelStyle();
mLabelSuffix = mListener->getLabelSuffix();
}
updateExtraSearchCriteria();
}
}
void LLFolderViewItem::updateExtraSearchCriteria()
{
if(!mListener)
return;
LLInventoryItem* item = gInventory.getItem(mListener->getUUID());
std::string desc;
if (item)
{
if (!item->getDescription().empty())
{
desc = item->getDescription();
LLStringUtil::toUpper(desc);
}
}
mSearchableLabelDesc = desc;
std::string creator_name;
if (item)
{
if (item->getCreatorUUID().notNull())
{
gCacheName->getFullName(item->getCreatorUUID(), creator_name);
LLStringUtil::toUpper(creator_name);
}
}
mSearchableLabelCreator = creator_name;
updateSearchLabelType();
}
void LLFolderViewItem::refresh()
{
refreshFromListener();
std::string searchable_label(mLabel);
searchable_label.append(mLabelSuffix);
LLStringUtil::toUpper(searchable_label);
if (mSearchableLabel.compare(searchable_label))
{
mSearchableLabel.assign(searchable_label);
updateSearchLabelType();
dirtyFilter();
// some part of label has changed, so overall width has potentially changed, and sort order too
if (mParentFolder)
{
mParentFolder->requestSort();
mParentFolder->requestArrange();
}
}
mLabelWidthDirty = true;
}
void LLFolderViewItem::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor)
{
functor(mListener);
}
// This function is called when items are added or view filters change. It's
// implemented here but called by derived classes when folding the
// views.
void LLFolderViewItem::filterFromRoot( void )
{
LLFolderViewItem* root = getRoot();
root->filter(*((LLFolderView*)root)->getFilter());
}
// This function is called when the folder view is dirty. It's
// implemented here but called by derived classes when folding the
// views.
void LLFolderViewItem::arrangeFromRoot()
{
LLFolderViewItem* root = getRoot();
S32 height = 0;
S32 width = 0;
S32 total_height = root->arrange( &width, &height, 0 );
LLSD params;
params["action"] = "size_changes";
params["height"] = total_height;
getParent()->notifyParent(params);
}
// Utility function for LLFolderView
void LLFolderViewItem::arrangeAndSet(BOOL set_selection,
BOOL take_keyboard_focus)
{
LLFolderView* root = getRoot();
if (getParentFolder())
{
getParentFolder()->requestArrange();
}
if(set_selection)
{
setSelectionFromRoot(this, TRUE, take_keyboard_focus);
if(root)
{
root->scrollToShowSelection();
}
}
}
// This function clears the currently selected item, and records the
// specified selected item appropriately for display and use in the
// UI. If open is TRUE, then folders are opened up along the way to
// the selection.
void LLFolderViewItem::setSelectionFromRoot(LLFolderViewItem* selection,
BOOL openitem,
BOOL take_keyboard_focus)
{
getRoot()->setSelection(selection, openitem, take_keyboard_focus);
}
// helper function to change the selection from the root.
void LLFolderViewItem::changeSelectionFromRoot(LLFolderViewItem* selection, BOOL selected)
{
getRoot()->changeSelection(selection, selected);
}
std::set<LLUUID> LLFolderViewItem::getSelectionList() const
{
std::set<LLUUID> selection;
return selection;
}
EInventorySortGroup LLFolderViewItem::getSortGroup() const
{
return SG_ITEM;
}
// addToFolder() returns TRUE if it succeeds. FALSE otherwise
BOOL LLFolderViewItem::addToFolder(LLFolderViewFolder* folder, LLFolderView* root)
{
if (!folder)
{
return FALSE;
}
mParentFolder = folder;
root->addItemID(getListener()->getUUID(), this);
return folder->addItem(this);
}
// Finds width and height of this object and it's children. Also
// makes sure that this view and it's children are the right size.
S32 LLFolderViewItem::arrange( S32* width, S32* height, S32 filter_generation)
{
S32 indentation = LEFT_INDENTATION;
// Only indent deeper items in hierarchy
mIndentation = (getParentFolder()
&& getParentFolder()->getParentFolder() )
? mParentFolder->getIndentation() + indentation
: 0;
if (mLabelWidthDirty)
{
mLabelWidth = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + getLabelFontForStyle(mLabelStyle)->getWidth(mSearchableLabel);
mLabelWidthDirty = false;
}
*width = llmax(*width, mLabelWidth + mIndentation);
// 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;
}
void LLFolderViewItem::filter( LLInventoryFilter& filter)
{
const BOOL previous_passed_filter = mPassedFilter;
const BOOL passed_filter = filter.check(this);
// If our visibility will change as a result of this filter, then
// we need to be rearranged in our parent folder
if (mParentFolder)
{
if (getVisible() != passed_filter
|| previous_passed_filter != passed_filter )
mParentFolder->requestArrange();
}
setFiltered(passed_filter, filter.getCurrentGeneration());
mStringMatchOffset = filter.getStringMatchOffset();
// If Creator is part of the filter, don't let it get highlighted if it matches
if (mSearchType & 4 && mStringMatchOffset >= mSearchable.length()-mSearchableLabelCreator.length())
mStringMatchOffset = std::string::npos;
filter.decrementFilterCount();
if (getRoot()->getDebugFilters())
{
mStatusText = llformat("%d", mLastFilterGeneration);
}
}
void LLFolderViewItem::dirtyFilter()
{
mLastFilterGeneration = -1;
// bubble up dirty flag all the way to root
if (getParentFolder())
{
getParentFolder()->setCompletedFilterGeneration(-1, TRUE);
}
}
// *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)
{
if (mListener)
{
mListener->selectItem();
}
mIsSelected = TRUE;
}
}
BOOL LLFolderViewItem::isMovable()
{
if( mListener )
{
return mListener->isItemMovable();
}
else
{
return TRUE;
}
}
BOOL LLFolderViewItem::isRemovable()
{
if( mListener )
{
return mListener->isItemRemovable();
}
else
{
return TRUE;
}
}
void LLFolderViewItem::destroyView()
{
if (mParentFolder)
{
// removeView deletes me
mParentFolder->removeView(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;
}
if(mListener)
{
return mListener->removeItem();
}
return TRUE;
}
// Build an appropriate context menu for the item.
void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
{
if(mListener)
{
mListener->buildContextMenu(menu, flags);
}
}
void LLFolderViewItem::openItem( void )
{
if( mListener )
{
mListener->openItem();
}
}
void LLFolderViewItem::preview( void )
{
if (mListener)
{
mListener->previewItem();
}
}
void LLFolderViewItem::rename(const std::string& new_name)
{
if( !new_name.empty() )
{
mLabel = new_name;
if( mListener )
{
mListener->renameItem(new_name);
if(mParentFolder)
{
mParentFolder->requestSort();
}
}
}
}
void LLFolderViewItem::nameOrDescriptionChanged(void) const
{
// Inform the listeners. Our name was already updated (and we don't have a description).
if( mListener )
{
mListener->nameOrDescriptionChanged();
}
}
void LLFolderViewItem::updateSearchLabelType()
{
mSearchType = mRoot->getSearchType();
mSearchable.erase();
if (!mSearchType || mSearchType & 1)
mSearchable = mSearchableLabel;
if (mSearchType & 2)
{
if (mSearchable.length())
{
mSearchable += " ";
}
mSearchable += mSearchableLabelDesc;
}
if (mSearchType & 4)
{
if (mSearchable.length())
{
mSearchable += " ";
}
mSearchable += mSearchableLabelCreator;
}
}
const std::string& LLFolderViewItem::getSearchableLabel()
{
if(mSearchType != mRoot->getSearchType())
updateSearchLabelType();
return mSearchable;
}
LLViewerInventoryItem * LLFolderViewItem::getInventoryItem(void)
{
if (!getListener()) return NULL;
return gInventory.getItem(getListener()->getUUID());
}
const std::string& LLFolderViewItem::getName( void ) const
{
if(mListener)
{
return mListener->getName();
}
return mLabel;
}
// LLView functionality
BOOL LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
if(!mIsSelected)
{
setSelectionFromRoot(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)
{
changeSelectionFromRoot(this, !mIsSelected);
}
else if (mask & MASK_SHIFT)
{
getParentFolder()->extendSelectionTo(this);
}
else
{
setSelectionFromRoot(this, FALSE);
}
make_ui_sound("UISndClick");
}
else
{
mSelectPending = TRUE;
}
if( isMovable() )
{
S32 screen_x;
S32 screen_y;
localPointToScreen(x, y, &screen_x, &screen_y );
LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y );
}
return TRUE;
}
BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
{
if( hasMouseCapture() && isMovable() )
{
S32 screen_x;
S32 screen_y;
localPointToScreen(x, y, &screen_x, &screen_y );
BOOL can_drag = TRUE;
if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) )
{
LLFolderView* root = getRoot();
if(root->getCurSelectedItem())
{
LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_WORLD;
// *TODO: push this into listener and remove
// dependency on llagent
if (mListener
&& gInventory.isObjectDescendentOf(mListener->getUUID(), gInventory.getRootFolderID()))
{
src = LLToolDragAndDrop::SOURCE_AGENT;
}
else if (mListener
&& gInventory.isObjectDescendentOf(mListener->getUUID(), gInventory.getLibraryRootFolderID()))
{
src = LLToolDragAndDrop::SOURCE_LIBRARY;
}
can_drag = root->startDrag(src);
if (can_drag)
{
// if (mListener) mListener->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);
return LLToolDragAndDrop::getInstance()->handleHover( screen_x, screen_y, mask );
}
}
}
if (can_drag)
{
gViewerWindow->setCursor(UI_CURSOR_ARROW);
}
else
{
gViewerWindow->setCursor(UI_CURSOR_NOLOCKED);
}
return TRUE;
}
else
{
getRoot()->setShowSelectionContext(FALSE);
gViewerWindow->setCursor(UI_CURSOR_ARROW);
// let parent handle this then...
return FALSE;
}
}
BOOL LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask )
{
preview();
return TRUE;
}
BOOL LLFolderViewItem::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
if (getParent())
{
return getParent()->handleScrollWheel(x, y, clicks);
}
return FALSE;
}
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)
{
changeSelectionFromRoot(this, !mIsSelected);
}
else if (mask & MASK_SHIFT)
{
getParentFolder()->extendSelectionTo(this);
}
else
{
setSelectionFromRoot(this, FALSE);
}
}
mSelectPending = FALSE;
if( hasMouseCapture() )
{
getRoot()->setShowSelectionContext(FALSE);
gFocusMgr.setMouseCapture( NULL );
}
return TRUE;
}
BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
EDragAndDropType cargo_type,
void* cargo_data,
EAcceptance* accept,
std::string& tooltip_msg)
{
BOOL accepted = FALSE;
BOOL handled = FALSE;
if(mListener)
{
accepted = mListener->dragOrDrop(mask,drop,cargo_type,cargo_data);
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::draw()
{
static LLCachedControl<LLColor4> sFgColor(gColors, "MenuItemEnabledColor", LLColor4::white );
static LLCachedControl<LLColor4> sHighlightBgColor(gColors, "MenuItemHighlightBgColor", LLColor4::white );
static LLCachedControl<LLColor4> sHighlightFgColor(gColors, "MenuItemHighlightFgColor", LLColor4::white );
static LLCachedControl<LLColor4> sFilterBGColor(gColors, "FilterBackgroundColor", LLColor4::white );
static LLCachedControl<LLColor4> sFilterTextColor(gColors, "FilterTextColor", LLColor4::white );
static LLCachedControl<LLColor4> sSuffixColor(gColors, "InventoryItemSuffixColor", LLColor4::white );
static LLCachedControl<LLColor4> sSearchStatusColor(gColors, "InventorySearchStatusColor", LLColor4::white );
const S32 TOP_PAD = 4;
const S32 FOCUS_LEFT = 1;
const LLFontGL* font = getLabelFontForStyle(mLabelStyle);
const BOOL in_inventory = getListener() && gInventory.isObjectDescendentOf(getListener()->getUUID(), gInventory.getRootFolderID());
const BOOL in_library = getListener() && gInventory.isObjectDescendentOf(getListener()->getUUID(), gInventory.getLibraryRootFolderID());
//--------------------------------------------------------------------------------//
// Draw open folder arrow
//
const bool up_to_date = mListener && mListener->isUpToDate();
const bool possibly_has_children = ((up_to_date && hasVisibleChildren()) // we fetched our children and some of them have passed the filter...
|| (!up_to_date && mListener && mListener->hasChildren())); // ...or we know we have children but haven't fetched them (doesn't obey filter)
if (possibly_has_children && sArrowImage)
{
gl_draw_scaled_rotated_image(
mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD - TOP_PAD,
ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, sArrowImage->getImage(), sFgColor);
}
//--------------------------------------------------------------------------------//
// Draw highlight for selected items
//
const BOOL show_context = getRoot()->getShowSelectionContext();
const BOOL filled = show_context || (getRoot()->getParentPanel()->hasFocus()); // If we have keyboard focus, draw selection filled
const S32 focus_top = getRect().getHeight();
const S32 focus_bottom = getRect().getHeight() - mItemHeight;
const bool folder_open = (getRect().getHeight() > mItemHeight + 4);
if (mIsSelected) // always render "current" item. Only render other selected items if mShowSingleSelection is FALSE
{
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
LLColor4 bg_color = sHighlightBgColor;
if (!mIsCurSelection)
{
// do time-based fade of extra objects
F32 fade_time = getRoot()->getSelectionFadeElapsedTime();
if (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, filled);
if (mIsCurSelection)
{
gl_rect_2d(FOCUS_LEFT,
focus_top,
getRect().getWidth() - 2,
focus_bottom,
sHighlightFgColor, FALSE);
}
if (folder_open)
{
gl_rect_2d(FOCUS_LEFT,
focus_bottom + 1, // overlap with bottom edge of above rect
getRect().getWidth() - 2,
0,
sHighlightFgColor, FALSE);
if (show_context)
{
gl_rect_2d(FOCUS_LEFT,
focus_bottom + 1,
getRect().getWidth() - 2,
0,
sHighlightBgColor, TRUE);
}
}
}
//--------------------------------------------------------------------------------//
// Draw DragNDrop highlight
//
if (mDragAndDropTarget)
{
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gl_rect_2d(FOCUS_LEFT,
focus_top,
getRect().getWidth() - 2,
focus_bottom,
sHighlightBgColor, FALSE);
if (folder_open)
{
gl_rect_2d(FOCUS_LEFT,
focus_bottom + 1, // overlap with bottom edge of above rect
getRect().getWidth() - 2,
0,
sHighlightBgColor, FALSE);
}
mDragAndDropTarget = FALSE;
}
const LLViewerInventoryItem *item = getInventoryItem();
const BOOL highlight_link = mIconOverlay && item && item->getIsLinkType();
//--------------------------------------------------------------------------------//
// Draw open icon
//
const S32 icon_x = mIndentation + ARROW_SIZE + TEXT_PAD;
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 (highlight_link)
{
mIconOverlay->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1);
}
//--------------------------------------------------------------------------------//
// Exit if no label to draw
//
if (mLabel.empty())
{
return;
}
LLColor4 color = (mIsSelected && filled) ? sHighlightFgColor : sFgColor;
F32 right_x = 0;
F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD - (F32)TOP_PAD;
F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation);
//--------------------------------------------------------------------------------//
// Highlight filtered text
//
if (getRoot()->getDebugFilters())
{
if (!getFiltered() && !possibly_has_children)
{
color.mV[VALPHA] *= 0.5f;
}
LLColor4 filter_color = mLastFilterGeneration >= getRoot()->getFilter()->getCurrentGeneration() ?
LLColor4(0.5f, 0.8f, 0.5f, 1.f) :
LLColor4(0.8f, 0.5f, 0.5f, 1.f);
LLFontGL::getFontMonospace()->renderUTF8(mStatusText, 0, text_left, y, filter_color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, S32_MAX, &right_x, FALSE );
text_left = right_x;
}
//--------------------------------------------------------------------------------//
// Draw the actual label text
//
font->renderUTF8(mLabel, 0, text_left, y, color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, getRect().getWidth() - (S32) text_left, &right_x, TRUE);
//--------------------------------------------------------------------------------//
// Draw "Loading..." text
//
bool root_is_loading = false;
if (in_inventory)
{
root_is_loading = LLInventoryModelBackgroundFetch::instance().inventoryFetchInProgress();
}
if (in_library)
{
root_is_loading = LLInventoryModelBackgroundFetch::instance().libraryFetchInProgress();
}
if ((mIsLoading
&& mTimeSinceRequestStart.getElapsedTimeF32() >= gSavedSettings.getF32("FolderLoadingMessageWaitTime"))
|| (LLInventoryModelBackgroundFetch::instance().folderFetchActive()
&& root_is_loading
&& mShowLoadStatus))
{
static std::string const load_string = " ( " + LLTrans::getString("LoadingData") + " ) ";
font->renderUTF8(load_string, 0, right_x, y, sSearchStatusColor,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, S32_MAX, &right_x, FALSE);
}
//--------------------------------------------------------------------------------//
// Draw label suffix
//
if (!mLabelSuffix.empty())
{
font->renderUTF8( mLabelSuffix, 0, right_x, y, sSuffixColor,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, S32_MAX, &right_x, FALSE );
}
//--------------------------------------------------------------------------------//
// Highlight string match
//
if (sBoxImage.notNull() && mStringMatchOffset != std::string::npos)
{
// don't draw backgrounds for zero-length strings
S32 filter_string_length = getRoot()->getFilterSubString().size();
if (filter_string_length > 0 && (mRoot->getSearchType() & 1))
{
std::string combined_string = mLabel + mLabelSuffix;
S32 left = ll_round(text_left) + font->getWidth(combined_string, 0, mStringMatchOffset) - 1;
S32 right = left + font->getWidth(combined_string, mStringMatchOffset, filter_string_length) + 2;
S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD);
S32 top = getRect().getHeight() - TOP_PAD;
LLUIImage* box_image = sBoxImage;
LLRect box_rect(left, top, right, bottom);
box_image->draw(box_rect, sFilterBGColor);
F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, mStringMatchOffset);
F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD - (F32)TOP_PAD;
font->renderUTF8( combined_string, mStringMatchOffset, match_string_left, yy,
sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
filter_string_length, S32_MAX, &right_x, FALSE );
}
}
if( sDebugRects )
{
drawDebugRect();
}
}
///----------------------------------------------------------------------------
/// Class LLFolderViewFolder
///----------------------------------------------------------------------------
// Default constructor
LLFolderViewFolder::LLFolderViewFolder( const std::string& name, LLUIImagePtr icon,
LLUIImagePtr icon_open,
LLUIImagePtr icon_link,
LLFolderView* root,
LLFolderViewEventListener* listener ):
LLFolderViewItem( name, icon, icon_open, icon_link, 0, root, listener ), // 0 = no create time
mIsOpen(FALSE),
mExpanderHighlighted(FALSE),
mCurHeight(0.f),
mTargetHeight(0.f),
mAutoOpenCountdown(0.f),
mSubtreeCreationDate(0),
mAmTrash(LLFolderViewFolder::UNKNOWN),
mLastArrangeGeneration( -1 ),
mLastCalculatedWidth(0),
mCompletedFilterGeneration(-1),
mMostFilteredDescendantGeneration(-1),
mNeedsSort(false),
mPassedFolderFilter(FALSE)
{
}
// 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()
}
void LLFolderViewFolder::setFilteredFolder(bool filtered, S32 filter_generation)
{
mPassedFolderFilter = filtered;
mLastFilterGeneration = filter_generation;
}
bool LLFolderViewFolder::getFilteredFolder(S32 filter_generation)
{
return mPassedFolderFilter && mLastFilterGeneration >= getRoot()->getFilter()->getFirstSuccessGeneration();
}
// addToFolder() returns TRUE if it succeeds. FALSE otherwise
BOOL LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder, LLFolderView* root)
{
if (!folder)
{
return FALSE;
}
mParentFolder = folder;
root->addItemID(getListener()->getUUID(), this);
return folder->addFolder(this);
}
// Finds width and height of this object and its children. Also
// makes sure that this view and its children are the right size.
S32 LLFolderViewFolder::arrange( S32* width, S32* height, S32 filter_generation)
{
// sort before laying out contents
if (mNeedsSort)
{
mFolders.sort(mSortFunction);
mItems.sort(mSortFunction);
mNeedsSort = false;
}
mHasVisibleChildren = hasFilteredDescendants(filter_generation);
// calculate height as a single item (without any children), and reshapes rectangle to match
LLFolderViewItem::arrange( width, height, filter_generation );
// 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
*height = getItemHeight();
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 (mIsOpen)
{
// Add sizes of children
S32 parent_item_height = getRect().getHeight();
for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit)
{
LLFolderViewFolder* folderp = (*fit);
if (getRoot()->getDebugFilters())
{
folderp->setVisible(TRUE);
}
else
{
folderp->setVisible( folderp->getListener()
&& (folderp->getFiltered(filter_generation)
|| (folderp->getFilteredFolder(filter_generation)
&& folderp->hasFilteredDescendants(filter_generation)))); // passed filter or has descendants that passed filter
}
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, filter_generation );
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);
if (getRoot()->getDebugFilters())
{
itemp->setVisible(TRUE);
}
else
{
itemp->setVisible(itemp->getFiltered(filter_generation));
}
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, filter_generation );
// 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(mIsOpen ? 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) + MAX_FOLDER_ITEM_OVERLAP)
{
// 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) + MAX_FOLDER_ITEM_OVERLAP)
{
(*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();
}
void LLFolderViewFolder::requestSort()
{
mNeedsSort = true;
// whenever item order changes, we need to lay things out again
requestArrange();
}
void LLFolderViewFolder::setCompletedFilterGeneration(S32 generation, BOOL recurse_up)
{
mMostFilteredDescendantGeneration = llmin(mMostFilteredDescendantGeneration, generation);
mCompletedFilterGeneration = generation;
// only aggregate up if we are a lower (older) value
if (recurse_up
&& mParentFolder
&& generation < mParentFolder->getCompletedFilterGeneration())
{
mParentFolder->setCompletedFilterGeneration(generation, TRUE);
}
}
void LLFolderViewFolder::filter( LLInventoryFilter& filter)
{
S32 filter_generation = filter.getCurrentGeneration();
// if failed to pass filter newer than must_pass_generation
// you will automatically fail this time, so we only
// check against items that have passed the filter
S32 must_pass_generation = filter.getFirstRequiredGeneration();
bool autoopen_folders = (filter.hasFilterString());
// if we have already been filtered against this generation, skip out
if (getCompletedFilterGeneration() >= filter_generation)
{
return;
}
// filter folder itself
if (getLastFilterGeneration() < filter_generation)
{
if (getLastFilterGeneration() >= must_pass_generation // folder has been compared to a valid precursor filter
&& !mPassedFilter) // and did not pass the filter
{
// go ahead and flag this folder as done
mLastFilterGeneration = filter_generation;
}
else // filter self only on first pass through
{
// filter against folder rules
filterFolder(filter);
// and then item rules
LLFolderViewItem::filter( filter );
}
}
if (getRoot()->getDebugFilters())
{
mStatusText = llformat("%d", mLastFilterGeneration);
mStatusText += llformat("(%d)", mCompletedFilterGeneration);
mStatusText += llformat("+%d", mMostFilteredDescendantGeneration);
}
// all descendants have been filtered later than must pass generation
// but none passed
if(getCompletedFilterGeneration() >= must_pass_generation && !hasFilteredDescendants(must_pass_generation))
{
// don't traverse children if we've already filtered them since must_pass_generation
// and came back with nothing
return;
}
// we entered here with at least one filter iteration left
// check to see if we have any more before continuing on to children
if (filter.getFilterCount() < 0)
{
return;
}
// when applying a filter, matching folders get their contents downloaded first
if (filter.isNotDefault()
&& getFiltered(filter.getFirstSuccessGeneration())
&& (mListener
&& !gInventory.isCategoryComplete(mListener->getUUID())))
{
LLInventoryModelBackgroundFetch::instance().start(mListener->getUUID());
}
// now query children
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();
++iter)
{
LLFolderViewFolder* folder = (*iter);
// have we run out of iterations this frame?
if (filter.getFilterCount() < 0)
{
break;
}
// mMostFilteredDescendantGeneration might have been reset
// in which case we need to update it even for folders that
// don't need to be filtered anymore
if (folder->getCompletedFilterGeneration() >= filter_generation)
{
// track latest generation to pass any child items
if (folder->getFiltered() || folder->hasFilteredDescendants(filter.getFirstSuccessGeneration()))
{
mMostFilteredDescendantGeneration = filter_generation;
requestArrange();
}
// just skip it, it has already been filtered
continue;
}
// update this folders filter status (and children)
folder->filter( filter );
// track latest generation to pass any child items
if (folder->getFiltered() || folder->hasFilteredDescendants(filter_generation))
{
mMostFilteredDescendantGeneration = filter_generation;
requestArrange();
if (getRoot()->needsAutoSelect() && autoopen_folders)
{
folder->setOpenArrangeRecursively(TRUE);
}
}
}
for (items_t::iterator iter = mItems.begin();
iter != mItems.end();
++iter)
{
LLFolderViewItem* item = (*iter);
if (filter.getFilterCount() < 0)
{
break;
}
if (item->getLastFilterGeneration() >= filter_generation)
{
if (item->getFiltered())
{
mMostFilteredDescendantGeneration = filter_generation;
requestArrange();
}
continue;
}
if (item->getLastFilterGeneration() >= must_pass_generation &&
!item->getFiltered(must_pass_generation))
{
// failed to pass an earlier filter that was a subset of the current one
// go ahead and flag this item as done
item->setFiltered(FALSE, filter_generation);
continue;
}
item->filter( filter );
if (item->getFiltered(filter.getFirstSuccessGeneration()))
{
mMostFilteredDescendantGeneration = filter_generation;
requestArrange();
}
}
// if we didn't use all filter iterations
// that means we filtered all of our descendants
// instead of exhausting the filter count for this frame
if (filter.getFilterCount() > 0)
{
// flag this folder as having completed filter pass for all descendants
setCompletedFilterGeneration(filter_generation, FALSE/*dont recurse up to root*/);
}
}
void LLFolderViewFolder::filterFolder(LLInventoryFilter& filter)
{
const BOOL previous_passed_filter = mPassedFolderFilter;
const BOOL passed_filter = filter.checkFolder(this);
// If our visibility will change as a result of this filter, then
// we need to be rearranged in our parent folder
if (mParentFolder)
{
if (getVisible() != passed_filter
|| previous_passed_filter != passed_filter )
{
mParentFolder->requestArrange();
}
}
setFilteredFolder(passed_filter, filter.getCurrentGeneration());
filter.decrementFilterCount();
if (getRoot()->getDebugFilters())
{
mStatusText = llformat("%d", mLastFilterGeneration);
}
}
void LLFolderViewFolder::setFiltered(BOOL filtered, S32 filter_generation)
{
// if this folder is now filtered, but wasn't before
// (it just passed)
if (filtered && !mPassedFilter)
{
// reset current height, because last time we drew it
// it might have had more visible items than now
mCurHeight = 0.f;
}
LLFolderViewItem::setFiltered(filtered, filter_generation);
}
void LLFolderViewFolder::dirtyFilter()
{
// we're a folder, so invalidate our completed generation
setCompletedFilterGeneration(-1, FALSE);
LLFolderViewItem::dirtyFilter();
}
BOOL LLFolderViewFolder::getFiltered()
{
return getFilteredFolder(getRoot()->getFilter()->getFirstSuccessGeneration())
&& LLFolderViewItem::getFiltered();
}
BOOL LLFolderViewFolder::getFiltered(S32 filter_generation)
{
return getFilteredFolder(filter_generation) && LLFolderViewItem::getFiltered(filter_generation);
}
BOOL LLFolderViewFolder::hasFilteredDescendants(S32 filter_generation)
{
return mMostFilteredDescendantGeneration >= filter_generation;
}
BOOL LLFolderViewFolder::hasFilteredDescendants()
{
return mMostFilteredDescendantGeneration >= getRoot()->getFilter()->getCurrentGeneration();
}
// 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)
{
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)
{
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)
{
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)
{
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();
for (std::vector<LLFolderViewItem*>::iterator it = items_to_select_forward.begin(), end_it = items_to_select_forward.end();
it != end_it;
++it)
{
LLFolderViewItem* item = *it;
if (item->isSelected())
{
root->removeFromSelectionList(item);
}
else
{
item->selectItem();
}
root->addToSelectionList(item);
}
if (new_selection->isSelected())
{
root->removeFromSelectionList(new_selection);
}
else
{
new_selection->selectItem();
}
root->addToSelectionList(new_selection);
}
void LLFolderViewFolder::destroyView()
{
for (items_t::iterator iter = mItems.begin();
iter != mItems.end();)
{
items_t::iterator iit = iter++;
LLFolderViewItem* item = (*iit);
getRoot()->removeItemID(item->getListener()->getUUID());
}
std::for_each(mItems.begin(), mItems.end(), DeletePointer());
mItems.clear();
while (!mFolders.empty())
{
LLFolderViewFolder *folderp = mFolders.back();
folderp->destroyView(); // removes entry from mFolders
}
//deleteAllChildren();
if (mParentFolder)
{
mParentFolder->removeView(this);
}
}
// remove the specified item (and any children) if possible. Return
// TRUE if the item was deleted.
BOOL LLFolderViewFolder::removeItem(LLFolderViewItem* item)
{
if(item->remove())
{
return TRUE;
}
return FALSE;
}
// simply remove the view (and any children) Don't bother telling the
// listeners.
void LLFolderViewFolder::removeView(LLFolderViewItem* item)
{
if (!item || item->getParentFolder() != this)
{
return;
}
// deselect without traversing hierarchy
if (item->isSelected())
{
item->deselectItem();
}
getRoot()->removeFromSelectionList(item);
extractItem(item);
delete item;
}
// extractItem() removes the specified item from the folder, but
// doesn't delete it.
void LLFolderViewFolder::extractItem( LLFolderViewItem* item )
{
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
dirtyFilter();
//because an item is going away regardless of filter status, force rearrange
requestArrange();
getRoot()->removeItemID(item->getListener()->getUUID());
removeChild(item);
}
bool LLFolderViewFolder::isTrash() const
{
if (mAmTrash == LLFolderViewFolder::UNKNOWN)
{
mAmTrash = mListener->getUUID() == gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH, false) ? LLFolderViewFolder::TRASH : LLFolderViewFolder::NOT_TRASH;
}
return mAmTrash == LLFolderViewFolder::TRASH;
}
void LLFolderViewFolder::sortBy(U32 order)
{
if (!mSortFunction.updateSort(order))
{
// No changes.
return;
}
// Propagate this change to sub folders
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();)
{
folders_t::iterator fit = iter++;
(*fit)->sortBy(order);
}
// Don't sort the topmost folders (My Inventory and Library)
if (mListener->getUUID().notNull())
{
mFolders.sort(mSortFunction);
mItems.sort(mSortFunction);
}
if (order & LLInventoryFilter::SO_DATE)
{
time_t latest = 0;
if (!mItems.empty())
{
LLFolderViewItem* item = *(mItems.begin());
latest = item->getCreationDate();
}
if (!mFolders.empty())
{
LLFolderViewFolder* folder = *(mFolders.begin());
if (folder->getCreationDate() > latest)
{
latest = folder->getCreationDate();
}
}
mSubtreeCreationDate = latest;
}
}
void LLFolderViewFolder::setItemSortOrder(U32 ordering)
{
if (mSortFunction.updateSort(ordering))
{
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();)
{
folders_t::iterator fit = iter++;
(*fit)->setItemSortOrder(ordering);
}
mFolders.sort(mSortFunction);
mItems.sort(mSortFunction);
}
}
EInventorySortGroup LLFolderViewFolder::getSortGroup() const
{
if (isTrash())
{
return SG_TRASH_FOLDER;
}
if( mListener )
{
if(LLFolderType::lookupIsProtectedType(mListener->getPreferredType()))
{
return SG_SYSTEM_FOLDER;
}
}
return SG_NORMAL_FOLDER;
}
BOOL LLFolderViewFolder::isMovable()
{
if( mListener )
{
if( !(mListener->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( mListener )
{
if( !(mListener->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.
BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item)
{
mItems.push_back(item);
item->setRect(LLRect(0, 0, getRect().getWidth(), 0));
item->setVisible(FALSE);
addChild(item);
item->dirtyFilter();
// Update the folder creation date if the folder has no creation date
bool setting_date = false;
const time_t item_creation_date = item->getCreationDate();
if ((item_creation_date > 0) && (mCreationDate == 0))
{
setCreationDate(item_creation_date);
setting_date = true;
}
// Handle sorting
requestArrange();
requestSort();
// Traverse parent folders and update creation date and resort, if necessary
LLFolderViewFolder* parentp = getParentFolder();
while (parentp)
{
// Update the parent folder creation date
if (setting_date && (parentp->mCreationDate == 0))
{
parentp->setCreationDate(item_creation_date);
}
if (parentp->mSortFunction.isByDate())
{
// parent folder doesn't have a time stamp yet, so get it from us
parentp->requestSort();
}
parentp = parentp->getParentFolder();
}
return TRUE;
}
// this is an internal method used for adding items to folders.
BOOL LLFolderViewFolder::addFolder(LLFolderViewFolder* folder)
{
mFolders.push_back(folder);
folder->setOrigin(0, 0);
folder->reshape(getRect().getWidth(), 0);
folder->setVisible(FALSE);
addChild( folder );
folder->dirtyFilter();
// rearrange all descendants too, as our indentation level might have changed
folder->requestArrange(TRUE);
requestSort();
LLFolderViewFolder* parentp = getParentFolder();
while (parentp && !parentp->mSortFunction.isByDate())
{
// parent folder doesn't have a time stamp yet, so get it from us
parentp->requestSort();
parentp = parentp->getParentFolder();
}
return TRUE;
}
void LLFolderViewFolder::requestArrange(BOOL include_descendants)
{
mLastArrangeGeneration = -1;
// flag all items up to root
if (mParentFolder)
{
mParentFolder->requestArrange();
}
if (include_descendants)
{
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();
++iter)
{
(*iter)->requestArrange(TRUE);
}
}
}
void LLFolderViewFolder::toggleOpen()
{
setOpen(!mIsOpen);
}
// Force a folder open or closed
void LLFolderViewFolder::setOpen(BOOL openitem)
{
setOpenArrangeRecursively(openitem);
}
void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse)
{
BOOL was_open = mIsOpen;
mIsOpen = openitem;
if (mListener)
{
if(!was_open && openitem)
{
mListener->openItem();
}
else if(was_open && !openitem)
{
mListener->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 != mIsOpen)
{
requestArrange();
}
}
BOOL LLFolderViewFolder::handleDragAndDropFromChild(MASK mask,
BOOL drop,
EDragAndDropType c_type,
void* cargo_data,
EAcceptance* accept,
std::string& tooltip_msg)
{
BOOL accepted = mListener && mListener->dragOrDrop(mask,drop,c_type,cargo_data);
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));
}
}
void LLFolderViewFolder::applyListenerFunctorRecursively(LLFolderViewListenerFunctor& functor)
{
functor(mListener);
for (folders_t::iterator iter = mFolders.begin();
iter != mFolders.end();)
{
folders_t::iterator fit = iter++;
(*fit)->applyListenerFunctorRecursively(functor);
}
for (items_t::iterator iter = mItems.begin();
iter != mItems.end();)
{
items_t::iterator iit = iter++;
(*iit)->applyListenerFunctorRecursively(functor);
}
}
// 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 (mIsOpen)
{
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)
{
BOOL accepted = mListener && mListener->dragOrDrop(mask, drop, cargo_type, cargo_data);
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;
// fetch contents of this folder, as context menu can depend on contents
// still, user would have to open context menu again to see the changes
gInventory.fetchDescendentsOf(mListener->getUUID());
if( mIsOpen )
{
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)
{
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( mIsOpen )
{
handled = childrenHandleMouseDown(x,y,mask) != NULL;
}
if( !handled )
{
if(mIndentation < x && x < mIndentation + ARROW_SIZE + TEXT_PAD)
{
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( mIsOpen )
{
handled = childrenHandleDoubleClick( x, y, mask ) != NULL;
}
if( !handled )
{
if(mIndentation < x && x < mIndentation + ARROW_SIZE + TEXT_PAD)
{
// don't select when user double-clicks plus sign
// so as not to contradict single-click behavior
toggleOpen();
}
else
{
setSelectionFromRoot(this, FALSE);
toggleOpen();
}
handled = TRUE;
}
return handled;
}
void LLFolderViewFolder::draw()
{
if (mAutoOpenCountdown != 0.f)
{
mControlLabelRotation = mAutoOpenCountdown * -90.f;
}
else if (mIsOpen)
{
mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLSmoothInterpolation::getInterpolant(0.04f));
}
else
{
mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLSmoothInterpolation::getInterpolant(0.025f));
}
bool possibly_has_children = false;
bool up_to_date = mListener && mListener->isUpToDate();
if(!up_to_date
&& mListener
&& mListener->hasChildren()) // we know we have children but haven't fetched them (doesn't obey filter)
{
possibly_has_children = true;
}
BOOL loading = (mIsOpen
&& possibly_has_children
&& !up_to_date );
if ( loading && !mIsLoading )
{
// Measure how long we've been in the loading state
mTimeSinceRequestStart.reset();
}
mIsLoading = loading;
LLFolderViewItem::draw();
// draw children if root folder, or any other folder that is open or animating to closed state
if( getRoot() == this || (mIsOpen || mCurHeight != mTargetHeight ))
{
LLView::draw();
}
mExpanderHighlighted = FALSE;
}
time_t LLFolderViewFolder::getCreationDate() const
{
return llmax<time_t>(mCreationDate, mSubtreeCreationDate);
}
BOOL LLFolderViewFolder::potentiallyVisible()
{
// folder should be visible by it's own filter status
return LLFolderViewItem::potentiallyVisible()
// or one or more of its descendants have passed the minimum filter requirement
|| hasFilteredDescendants(getRoot()->getFilter()->getFirstSuccessGeneration())
// or not all of its descendants have been checked against minimum filter requirement
|| getCompletedFilterGeneration() < getRoot()->getFilter()->getFirstSuccessGeneration();
}
// 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())
{
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;
}
bool LLInventorySort::updateSort(U32 order)
{
if (order != mSortOrder)
{
mSortOrder = order;
mByDate = (order & LLInventoryFilter::SO_DATE);
mSystemToTop = (order & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP);
mFoldersByName = (order & LLInventoryFilter::SO_FOLDERS_BY_NAME);
return true;
}
return false;
}
bool LLInventorySort::operator()(const LLFolderViewItem* const& a, const LLFolderViewItem* const& b)
{
/* TO-DO
// ignore sort order for landmarks in the Favorites folder.
// they should be always sorted as in Favorites bar. See EXT-719
if (a->getSortGroup() == SG_ITEM
&& b->getSortGroup() == SG_ITEM
&& a->getListener()->getInventoryType() == LLInventoryType::IT_LANDMARK
&& b->getListener()->getInventoryType() == LLInventoryType::IT_LANDMARK)
{
static const LLUUID& favorites_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE);
LLUUID a_uuid = a->getParentFolder()->getListener()->getUUID();
LLUUID b_uuid = b->getParentFolder()->getListener()->getUUID();
if ((a_uuid == favorites_folder_id && b_uuid == favorites_folder_id))
{
// *TODO: mantipov: probably it is better to add an appropriate method to LLFolderViewItem
// or to LLInvFVBridge
LLViewerInventoryItem* aitem = (static_cast<const LLItemBridge*>(a->getListener()))->getItem();
LLViewerInventoryItem* bitem = (static_cast<const LLItemBridge*>(b->getListener()))->getItem();
if (!aitem || !bitem)
return false;
S32 a_sort = aitem->getSortField();
S32 b_sort = bitem->getSortField();
return a_sort < b_sort;
}
}*/
// We sort by name if we aren't sorting by date
// OR if these are folders and we are sorting folders by name.
bool by_name = (!mByDate
|| (mFoldersByName
&& (a->getSortGroup() != SG_ITEM)));
if (a->getSortGroup() != b->getSortGroup())
{
if (mSystemToTop)
{
// Group order is System Folders, Trash, Normal Folders, Items
return (a->getSortGroup() < b->getSortGroup());
}
else if (mByDate)
{
// Trash needs to go to the bottom if we are sorting by date
if ( (a->getSortGroup() == SG_TRASH_FOLDER)
|| (b->getSortGroup() == SG_TRASH_FOLDER))
{
return (b->getSortGroup() == SG_TRASH_FOLDER);
}
}
}
if (by_name)
{
S32 compare = LLStringUtil::compareDict(a->getLabel(), b->getLabel());
if (0 == compare)
{
return (a->getCreationDate() > b->getCreationDate());
}
else
{
return (compare < 0);
}
}
else
{
// BUG: This is very very slow. The getCreationDate() is log n in number
// of inventory items.
time_t first_create = a->getCreationDate();
time_t second_create = b->getCreationDate();
if (first_create == second_create)
{
return (LLStringUtil::compareDict(a->getLabel(), b->getLabel()) < 0);
}
else
{
return (first_create > second_create);
}
}
}